[ПОСЛЕДНИЙ РАЗ] Прочитайте все знания, связанные с прототипами JS, в одной статье.

JavaScript внешний фреймворк
[ПОСЛЕДНИЙ РАЗ] Прочитайте все знания, связанные с прототипами JS, в одной статье.

предисловие

The last time, I have learned

[ПОСЛЕДНИЙ РАЗ] всегда был серией, которую я хотел написать, стремясь пережить интерфейс через накопление.

Это также для их собственных упущений и обмена технологиями.

Приветствуем всех, кто комментирует и указывает на Tucao.

Цикл статей впервые опубликован на официальном аккаунте [Full-Stack Front-End Selection].Сборник статей автора можно найти по адресу GitHub:Nealyang/personalBlog. Содержание и порядок публикации являются предварительными

Прежде всего, я хочу сказать, что содержание серии [ПОСЛЕДНИЙ РАЗ] всегда включало, но не ограничивалось рамками названия.

Вернемся к прототипу, старомодному вопросу. Но верно то, что многие квалифицированные рабочие, кажется, не разобрались в этом ясно.FunctionиObject,prototypeи__proto__В этой статье будет представлено систематическое введение в наследование JavaScript от прототипа к наследованию и к реализации синтаксического сахара es6. Если вы сможете ответить на следующие вопросы, то, судья, вам больше не нужно тратить время на чтение этой статьи~

  • ПочемуtypeofсудитьnullдаObjectтип?
  • FunctionиObjectКаковы отношения?
  • newЧто именно делают ключевые слова? Рукописная реализация.
  • prototypeи__proto__Каковы отношения? Равенство при каких обстоятельствах?
  • В ES5 есть несколько способов реализации наследования, в чем их преимущества и недостатки?
  • Как реализовать класс в ES6
  • ES6 extendsКаков принцип реализации ключевых слов?

Если у вас есть какие-то сомнения по поводу приведенных выше вопросов~ тогда. . .

ПОСЛЕДНИЙ РАЗ Обзор коллекции

содержание

Хотя статья длиннее, она более базовая. Прочитайте необходимые главы по мере необходимости.

Обратите внимание, что в конце статьи есть вопросы для размышления~

  • прототип шаттла
    • Функциональные объекты и обычные объекты
    • __proto__
    • prototype
    • constructor
  • Анализ принципа typeof && instanceof
    • Основное использование typeof
    • Анализ принципа typeof
    • пример базового использования
    • Анализ INSTANCEOF
  • ES5 Реализация наследства
    • новое ключевое слово
      • новая рукописная версия первая
      • новая рукописная версия 2
    • наследование классов
    • Наследование конструктора
    • наследование композиции
    • прототипное наследование
    • паразитарное наследование
    • Паразитическая композиционная наследственность
  • Принцип реализации класса ES6
    • базовый класс
    • добавить свойства
    • добавить метод
    • расширить ключевое слово
      • _inherits
      • _possibleConstructorReturn

прототип шаттла

Этот. . . Говорят, что оно самое основное и его никто не опровергает, что оно бесполезно и его никто не опровергает, и что многие до сих пор в нем не разобрались и никто его не опроверг! ОК~ Почему так много статей, а ты до сих пор не разобрался?

Пока не разобрались с концепцией, мы еще поставили старую так называемую классическую карту бога:

  • функция Foo — это метод, такой как встроенный массив, строка и т. д. в JavaScript.
  • объект функции есть объект
  • Функция Функция есть Функция
  • Выше перечислены все функции, поэтому.__proto__обаFunction.prototype
  • Опять же, строка, массив, число, функция, объект — все это функции.

Старое железо, если вам очень ясна эта картина, вы можете сразу пропустить эту главу

Старые правила, давайте сразу разберем понятия.

Функциональные объекты и обычные объекты

Как говорится в старой поговорке,все является объектом. И все мы знаем, что в JavaScript есть несколько способов создания объектов, таких как литералы объектов или новые объекты напрямую через конструктор:

На данный момент давайте проигнорируем значение приведенного выше кода. По крайней мере, мы можем видеть, чтоОба являются объектами, но есть различия

На самом деле в JavaScript мы делим объекты на функциональные объекты и обычные объекты.Так называемый функциональный объект на самом деле представляет собой реализацию класса JavaScript, которая использует функции для моделирования.. Объект и функция в JavaScript являются типичными функциональными объектами.

Что касается функциональных объектов и обычных объектов, то это самое интуитивное чувство. . . Давайте посмотрим непосредственно на код:

function fun1(){};
const fun2 = function(){};
const fun3 = new Function('name','console.log(name)');

const obj1 = {};
const obj2 = new Object();
const obj3 = new fun1();
const obj4 = new new Function();


console.log(typeof Object);//function
console.log(typeof Function);//function
console.log(typeof fun1);//function
console.log(typeof fun2);//function
console.log(typeof fun3);//function
console.log(typeof obj1);//object
console.log(typeof obj2);//object
console.log(typeof obj3);//object
console.log(typeof obj4);//object

Я не знаю, есть ли у вас какие-либо сомнения, когда вы видите приведенный выше код~ Не волнуйтесь, давайте разберемся с ним по крупицам.

В приведенном выше кодеobj1,obj2,obj3,obj4являются обычными объектами.fun1,fun2,fun3обаFunctionЭкземпляр , то есть функциональный объект.

Так что видно, чтоВсе экземпляры Function являются функциональными объектами, остальные являются обычными объектами, включая экземпляры экземпляров Function..

Все в JavaScript является объектом, а объекты происходят из конструкций (конструкторов)..

На картинке выше есть точка ваших сомнений?Functionиnew FunctionОтношение. На самом деле это так:

Function.__proto__ === Function.prototype//true

__proto__

Прежде всего, нам необходимо уточнить два момента: 1️⃣__proto__иconstructorдаобъектуникальный. 2️⃣prototypeсобственностьфункцияуникальный;

Но в JavaScript функции также являются объектами, поэтому функции также имеют__proto__иconstructorАтрибуты.

В сочетании с вышеперечисленнымObjectиFunctionОтношения, посмотрите на код и диаграмму отношений

 function Person(){…};
 let nealyang = new Person(); 

__proto__

Прежде чем разобраться в отношениях на приведенном выше рисунке, давайте еще раз объясним его.__proto__.

__proto__Пример , который сложнее сказать, можно сказать, исторический вопрос.

Описание спецификации ECMAScriptprototypeявляется неявной ссылкой, но некоторые предыдущие браузеры реализовали ее в частном порядке__proto__Это свойство позволяет пройтиobj.__proto__Этот явный доступ к свойству осуществляет доступ к свойству, определенному как неявное свойство.prototype.

Итак, вот ситуация, говорится в спецификации ECMAScript.prototypeДолжна быть неявная ссылка:

  • пройти черезObject.getPrototypeOf(obj)Косвенный доступ к указанному объектуprototypeобъект
  • пройти черезObject.setPrototypeOf(obj, anotherObj)Косвенно задает указанный объектprototypeобъект
  • Некоторые браузеры открываются раньше__proto__отверстия, которые позволяют проходитьobj.__proto__Прямой доступ к прототипу черезobj.__proto__ = anotherObjНастройте прототип напрямую
  • Спецификация ECMAScript 2015 должна была склониться перед фактами и__proto__Атрибуты включены как часть спецификации

Из результата печати браузера мы видим, что объект на приведенном выше рисункеaсуществует один__proto__Атрибуты. По сути, это просто преднамеренно визуализированный виртуальный узел инструмента разработчика для просмотра прототипа разработчиками. Хотя мы можем видеть его, на самом деле он не существует на объекте.

__proto__Свойства не могут быть ниfor inпройдено, это не может бытьObject.keys(obj)Выясни это.

объект доступаobj.__proto__атрибут, значение по умолчаниюObject.prototypeна объекте__proto__Методы получения/установки свойства.

Object.defineProperty(Object.prototype,'__proto__',{
	get(){
		console.log('get')
	}
});

({}).__proto__;
console.log((new Object()).__proto__);

о большем__proto__Для более подробного ознакомления вы можете обратиться к статье лидера отрасли «Углубленное понимание прототипов JavaScript».

Что нам нужно знать здесь, так это то,__proto__уникальна для объекта, и__proto__даОбъект указывает на другой объект, который является его объектом-прототипом. Мы также можем понимать его как объект родительского класса. Его функция заключается в том, что когда вы получаете доступ к свойству объекта, если свойство не существует в объекте, а затем возвращаетесь к его__proto__Поиск объекта, на который указывает атрибут (объект родительского класса), если объект родительского класса по-прежнему не имеет этого атрибута, затем вернуться к объекту родительского класса.__proto__Найдите родительский класс родительского класса, на который указывает свойство. И так далее, пока не найдетеnull. И этот процесс поиска составляет то, что мы часто говоримСеть прототипов.

prototype

object that provides shared properties for other objects

В спецификации прототип определяется как: объект, который предоставляет общие свойства другим объектам.prototypeЭто тоже объект, но он используется только для выполнения определенной функции.

Все объекты могут быть использованы как объекты другого объекта.prototypeиспользовать.

Исправлять__proto__, мы добавилиprototype,prototypeуникален для функции. ** Его роль состоит в том, чтобы содержать свойства и методы, которые могут быть общими для всех экземпляров определенного типа. Его значение — удаленный объект функции, ** — удаленный объект экземпляра, созданного этой функцией, как показано выше:nealyang.__proto__ === Person.prototype. При создании любой функции она будет добавлена ​​к функции по умолчанию.prototypeАтрибуты.

constructor

constructorСвойства также уникальны для объектов,этоОбъект указывает на функцию, которая является конструктором объекта.

Обратите внимание, что у каждого объекта есть соответствующий конструктор, либо он сам, либо унаследованный. сингл отconstructorТолько для этого объектаprototypeтолько объект. При создании каждой функции JavaScript также создаст соответствующую функцию.prototypeобъект и函数创建的对象.__proto__ === 该函数.prototype,Должен函数.prototype.constructor===该函数本身, поэтому объект, созданный функцией, даже если у него нетconstructorсобственность, она также может пройти__proto__найти соответствующийconstructor, поэтому любой объект может в конечном итоге найти соответствующий ему конструктор.

Единственная необычная возможность - это вопрос, который я задал в начале. Дедушка прототипов JavaScript:Function. Это собственный конструктор. такFunction.prototype === Function.__proto.

Для интуитивного понимания продолжаем добавлятьconstructor:

вconstructorАтрибуты,Пунктирная линия представляет унаследованное свойство конструктора..

__proto__Введенная цепочка прототипов, если ее визуально обозначить на рисунке, выглядит следующим образом.

Принцип Typeof && instanceof

Почему хорошо говорить о прототипах и наследовании, и это будет включать в себя принцип суждения о типах. Ведь есть незначительная связь в принципе, а часто и интервью от мелкого до глубокого, и выкручиваются все знания. Итак, давайте кратко поговорим об этом здесь.

typeof

Щелкните здесь, чтобы просмотреть документацию MDN:developer.Mozilla.org/this-cn/docs/…

Основное использование

typeofИспользование должно быть знакомо всем, и обычно оно используется для определения типа переменной. мы можем использоватьtypeofсудитьnumber,undefined,symbol,string,function,boolean,objectэти семь типов данных. Но, к сожалению,typeofв сужденииobjectТипа, немного стыдно. Это не говорит вам ясно, чтоobjectкакой видobject.

let s = new String('abc');
typeof s === 'object'// true
typeof null;//"object"

Принципиальный анализ

понять, почемуtypeofсудитьnullзаobject, на самом деле, нам нужно поговорить о том, как хранить типы переменных внизу js. Хотя это ошибка в дизайне JavaScript.

В исходной реализации JavaScript значение в JavaScript было представлено тегом, представляющим тип и фактическое значение данных. Метка типа объекта равна 0. так какnullпредставляет нулевой указатель (0x00 на большинстве платформ), поэтомуnullТиповая этикетка 0,typeof nullтакже вернуться"object". Было предложено исправление для ECMAScript (через подписку), нополучил отказ. Предложение приведет кtypeof null === 'null'.

Когда js хранит переменные внизу, информация о их типе будет храниться в младших 1-3 битах машинного кода переменной:

  • 1: целое число
  • 110: логическое значение
  • 100: Строка
  • 010: число с плавающей запятой
  • 000: Объект

Однако дляundefinedиnullХранение информации для этих двух значений немного особенное:

  • null: Все машинные коды равны 0
  • undefined: представлено как целое число −2^30

Итак, используяtypeofПри оценке типа переменной нам нужно обратить внимание, лучше всего использоватьtypeofопределить основные типы данных (включаяsymbol)nullСуждение

typeofПросто мы говорим о том, что дает прототипinstanceofДополнительный форум для

instanceof

object instanceof constructor

instanceofиtypeofочень похожий.instanceofоператор для обнаруженияconstructor.prototypeсуществует в параметреobjectв цепочке прототипов. иtypeofМетод отличается,instanceofМетоды требуют от разработчика явного подтверждения принадлежности объекта к определенному типу.

Основное использование

// 定义构造函数
function C(){} 
function D(){} 

var o = new C();


o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype


o instanceof D; // false,因为 D.prototype 不在 o 的原型链上

o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上

C.prototype = {};
var o2 = new C();

o2 instanceof C; // true

o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上.

D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上

как указано выше, даinstanceofОсновное использование , он может определить, является ли экземпляр экземпляром своего родительского типа или типом-предком.

console.log(Object instanceof Object);//true 
console.log(Function instanceof Function);//true 
console.log(Number instanceof Number);//false 
console.log(String instanceof String);//false 

console.log(Function instanceof Object);//true 

console.log(Foo instanceof Function);//true 
console.log(Foo instanceof Foo);//false

ПочемуObjectиFunction instanceofравный самому себеtrue, а другие классыinstanceofсам, но не равенtrueШерстяная ткань? Как объяснить?

Чтобы понять принципиальноinstanceofТайна , нам нужно начать с двух аспектов: 1. Как определить этот оператор в спецификации языка. 2, прототипный механизм наследования JavaScript.

Принципиальный анализ

После вышеприведенного анализа эта классическая карта бога не так уж незнакома всем, поэтому давайте поговорим об этой карте.instanceof

Здесь я напрямую перевожу определение спецификации в код JavaScript следующим образом:

function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
 var O = R.prototype;// 取 R 的显示原型
 L = L.__proto__;// 取 L 的隐式原型
 while (true) { 
   if (L === null) 
     return false; 
   if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true 
     return true; 
   L = L.__proto__; 
 } 
}

Итак, приведенный выше принцип плюс знания, связанные с описанным выше прототипом, давайте проанализируем, почемуObjectиFunction instanceofравный самому себеtrue.

  • Object instanceof Object
// 为了方便表述,首先区分左侧表达式和右侧表达式
ObjectL = Object, ObjectR = Object; 
// 下面根据规范逐步推演
O = ObjectR.prototype = Object.prototype 
L = ObjectL.__proto__ = Function.prototype 
// 第一次判断
O != L 
// 循环查找 L 是否还有 __proto__ 
L = Function.prototype.__proto__ = Object.prototype 
// 第二次判断
O == L 
// 返回 true
  • Function instanceof Function
// 为了方便表述,首先区分左侧表达式和右侧表达式
FunctionL = Function, FunctionR = Function; 
// 下面根据规范逐步推演
O = FunctionR.prototype = Function.prototype 
L = FunctionL.__proto__ = Function.prototype 
// 第一次判断
O == L 
// 返回 true
  • Foo instanceof Foo
// 为了方便表述,首先区分左侧表达式和右侧表达式
FooL = Foo, FooR = Foo; 
// 下面根据规范逐步推演
O = FooR.prototype = Foo.prototype 
L = FooL.__proto__ = Function.prototype 
// 第一次判断
O != L 
// 循环再次查找 L 是否还有 __proto__ 
L = Function.prototype.__proto__ = Object.prototype 
// 第二次判断
O != L 
// 再次循环查找 L 是否还有 __proto__ 
L = Object.prototype.__proto__ = null 
// 第三次判断
L == null 
// 返回 false

Реализация наследования в ES5

Что касается реализации наследования, Industrial Juda в своей статье-прототипе делит прототипное наследование на две категории: явное наследование и неявное наследование. Если вам интересно, вы можете щелкнуть ссылку в конце статьи для просмотра.

Однако в этой статье мы надеемся объяснить несколько распространенных методов наследования, их преимущества и недостатки, основанные на «популярном» методе. Можно многое сравнить и проверить, на самом деле принципы те же, а существительные - это просто так называемые местоимения.

Статьи о наследовании очень подробно объясняются во многих книгах и блогах. Следующие методы наследования обобщены в книге «Шаблоны проектирования JavaScript». Это статья, которую я написал три года назад.

новое ключевое слово

Прежде чем объяснять наследование, я думаюnewЭту вещь нужно представить~

посмотреть примерnewкакие ключевые слова

function Person(name,age){
  this.name = name;
  this.age = age;
  
  this.sex = 'male';
}

Person.prototype.isHandsome = true;

Person.prototype.sayName = function(){
  console.log(`Hello , my name is ${this.name}`);
}

let handsomeBoy = new Person('Nealyang',25);

console.log(handsomeBoy.name) // Nealyang
console.log(handsomeBoy.sex) // male
console.log(handsomeBoy.isHandsome) // true

handsomeBoy.sayName(); // Hello , my name is Nealyang

Из приведенного выше примера мы видим, что:

  • доступ кPersonсвойства в конструкторе
  • доступ кPerson.prototypeсвойства в

новая рукописная версия первая

function objectFactory() {

    const obj = new Object(),//从Object.prototype上克隆一个对象

    Constructor = [].shift.call(arguments);//取得外部传入的构造器

    const F=function(){};
    F.prototype= Constructor.prototype;
    obj=new F();//指向正确的原型

    Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性

    return obj;//返回 obj

};
  • использоватьnew Object()способ создать новый объект obj
  • Удалите первый параметр, который является конструктором, который мы хотим передать. Кроме того, поскольку сдвиг изменит исходный массив, поэтомуargumentsудалит первый параметр
  • Укажите прототип obj на конструктор, чтобы obj мог получить доступ к свойствам в прототипе конструктора
  • использоватьapply, изменение конструктораthisуказывает на вновь созданный объект, так что obj может получить доступ к свойствам в конструкторе
  • вернуть объект

Давайте проверим это:

function Person(name,age){
  this.name = name;
  this.age = age;
  
  this.sex = 'male';
}

Person.prototype.isHandsome = true;

Person.prototype.sayName = function(){
  console.log(`Hello , my name is ${this.name}`);
}

function objectFactory() {

    let obj = new Object(),//从Object.prototype上克隆一个对象

    Constructor = [].shift.call(arguments);//取得外部传入的构造器
    
    console.log({Constructor})

    const F=function(){};
    F.prototype= Constructor.prototype;
    obj=new F();//指向正确的原型

    Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性

    return obj;//返回 obj

};

let handsomeBoy = objectFactory(Person,'Nealyang',25);

console.log(handsomeBoy.name) // Nealyang
console.log(handsomeBoy.sex) // male
console.log(handsomeBoy.isHandsome) // true

handsomeBoy.sayName(); // Hello , my name is Nealyang

Обратите внимание, что мы не модифицировали obj выше.__proto__Неявное крепление.

новая рукописная версия 2

Рассмотрим случай, когда конструктор возвращает значение:

  • Если конструктор возвращает объект, то и мы возвращаем этот объект
  • В противном случае вернитесь к значению по умолчанию
function objectFactory() {

    var obj = new Object(),//从Object.prototype上克隆一个对象

    Constructor = [].shift.call(arguments);//取得外部传入的构造器

    var F=function(){};
    F.prototype= Constructor.prototype;
    obj=new F();//指向正确的原型

    var ret = Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性

    return typeof ret === 'object' ? ret : obj;//确保构造器总是返回一个对象

};

Об использовании и принципах call, apply, bind, this и т. д.:[ПОСЛЕДНИЙ РАЗ] это: позвонить, подать заявку, связать

наследование классов

function SuperClass() {
  this.superValue = true;
}
SuperClass.prototype.getSuperValue = function() {
  return this.superValue;
}

function SubClass() {
  this.subValue = false;
}
SubClass.prototype = new SuperClass();

SubClass.prototype.getSubValue = function() {
  return this.subValue;
}

var instance = new SubClass();

console.log(instance instanceof SuperClass)//true
console.log(instance instanceof SubClass)//true
console.log(SubClass instanceof SuperClass)//false

из нашего предыдущегоinstanceofПринцип, который мы знаем, третийconsoleЕсли да, верниtrueохватыватьconsole.log(SubClass.prototype instanceof SuperClass)

Хотя реализация понятна и лаконична, этот метод наследования имеет два недостатка:

  • Поскольку подкласс создает экземпляр родительского класса через свой прототип-прототип и наследует родительский класс, если общее свойство в родительском классе является ссылочным типом, оно будет общим для всех экземпляров в подклассе, поэтому экземпляр подкласса изменяется. общие свойства, унаследованные прототипом подкласса от конструктора родительского класса, будут напрямую влиять на другие подклассы
  • Поскольку наследование, реализованное подклассом, реализуется путем создания экземпляра родительского класса его прототипом-прототипом, невозможно передать параметры родительскому классу при создании родительского класса. Поэтому свойства в конструкторе родительского класса не могут быть инициализированы при создании экземпляра родительского класса.

Наследование конструктора

function SuperClass(id) {
  this.books = ['js','css'];
  this.id = id;
}
SuperClass.prototype.showBooks = function() {
  console.log(this.books);
}
function SubClass(id) {
  //继承父类
  SuperClass.call(this,id);
}
//创建第一个子类实例
var instance1 = new SubClass(10);
//创建第二个子类实例
var instance2 = new SubClass(11);

instance1.books.push('html');
console.log(instance1)
console.log(instance2)
instance1.showBooks();//TypeError

SuperClass.call(this,id)Конечно, это основной оператор наследования конструктора.Поскольку родительский класс привязывает к нему свойства, подкласс естественным образом наследует общие свойства родительского класса. Поскольку этот тип наследования не предполагает прототиповprototype, поэтому метод-прототип родительского класса не будет унаследован подклассом, но если он хочет быть унаследован подклассом, то его необходимо поместить в конструктор, чтобы каждый созданный экземпляр имел отдельную копию и не мог быть передан совместно , что нарушает принцип повторного использования кода, поэтому, объединив два вышеуказанных, мы предлагаем комбинированный метод наследования

наследование композиции

function SuperClass(name) {
  this.name = name; 
  this.books = ['Js','CSS'];
}
SuperClass.prototype.getBooks = function() {
    console.log(this.books);
}
function SubClass(name,time) {
  SuperClass.call(this,name);
  this.time = time;
}
SubClass.prototype = new SuperClass();

SubClass.prototype.getTime = function() {
  console.log(this.time);
}

Как и выше, мы решили некоторые из проблем, упомянутых ранее, но с точки зрения кода это все еще немного неудобно? по крайней мере этоSuperClassВыполнять конструктор дважды кажется очень неуместным.

прототипное наследование

function inheritObject(o) {
    //声明一个过渡对象
  function F() { }
  //过渡对象的原型继承父对象
  F.prototype = o;
  //返回过渡对象的实例,该对象的原型继承了父对象
  return new F();
}

Общая реализация прототипного наследования описана выше, вы подумали о нас?newВнедрение насмешек над ключевыми словами?

По сути, этот метод очень похож на наследование классов, это всего лишь инкапсуляция наследования классов, объект перехода в нем эквивалентен подклассу наследования классов, но он существует как обычный объект перехода в прототипном наследовании. для создания нового экземпляра объекта, который будет возвращен.

var book = {
    name:'js book',
    likeBook:['css Book','html book']
}
var newBook = inheritObject(book);
newBook.name = 'ajax book';
newBook.likeBook.push('react book');
var otherBook = inheritObject(book);
otherBook.name = 'canvas book';
otherBook.likeBook.push('node book');
console.log(newBook,otherBook);

Как мы видим из приведенного выше кода, прототипное наследование такое же, как и наследование класса.Для переменных ссылочного типа все еще существует ситуация, когда экземпляры подкласса являются общими.

Итак, у нас также есть следующее паразитное реле

паразитарное наследование

var book = {
    name:'js book',
    likeBook:['html book','css book']
}
function createBook(obj) {
    //通过原型方式创建新的对象
  var o = new inheritObject(obj);
  // 拓展新对象
  o.getName = function(name) {
    console.log(name)
  }
  // 返回拓展后的新对象
  return o;
}

По сути, паразитическое наследование — это расширение прототипного наследования, процесс вторичной инкапсуляции, так что вновь создаваемый объект не только имеет атрибуты и методы родительского класса, но и добавляет другие атрибуты и методы.

Паразитическая композиционная наследственность

Возвращаясь к предыдущему комбинированному наследованию, в то время мы использовали комбинацию наследования классов и наследования конструкторов, но проблема была в том, что подкласс не был экземпляром родительского класса, а прототип подкласса был экземпляром родительского класс, так что было Паразитическое Композиционное Наследование

Паразитическое наследование композиции представляет собой комбинацию паразитного наследования и наследования конструктора. Но паразитное наследование здесь немного особенное, где он имеет дело не с объектами, а с прототипами классов.

function inheritObject(o) {
  //声明一个过渡对象
  function F() { }
  //过渡对象的原型继承父对象
  F.prototype = o;
  //返回过渡对象的实例,该对象的原型继承了父对象
  return new F();
}

function inheritPrototype(subClass,superClass) {
    // 复制一份父类的原型副本到变量中
  var p = inheritObject(superClass.prototype);
  // 修正因为重写子类的原型导致子类的constructor属性被修改
  p.constructor = subClass;
  // 设置子类原型
  subClass.prototype = p;
}

В составном наследовании нет проблем со свойствами и методами, унаследованными через конструкторы, поэтому здесь мы в основном исследуем повторное наследование прототипа родительского класса посредством паразитного наследования.

Нам нужно наследовать только прототип родительского класса, без вызова конструктора родительского класса. Другими словами, при наследовании конструктора мы вызвали конструктор родительского класса. Следовательно, нам нужна копия объекта-прототипа родительского класса, и эта копия может быть получена путем прототипного наследования, но есть проблема в присвоении ее непосредственно подклассу, т.к. объект родительского классаconstructorсвойство указывает на неsubClassОбъекты подкласса, поэтому при паразитном наследовании объект копии p необходимо улучшить один раз, после чего начинается восстановление.constructorПроблема неправильного указания атрибута и, наконец, присвоения полученного объекта-копии p прототипу подкласса, чтобы прототип подкласса наследовал прототип родительского класса и не выполнял конструктор родительского класса.

function SuperClass(name) {
  this.name = name;
  this.books=['js book','css book'];
}
SuperClass.prototype.getName = function() {
  console.log(this.name);
}
function SubClass(name,time) {
  SuperClass.call(this,name);
  this.time = time;
}
inheritPrototype(SubClass,SuperClass);
SubClass.prototype.getTime = function() {
  console.log(this.time);
}
var instance1 = new SubClass('React','2017/11/11')
var instance2 = new SubClass('Js','2018/22/33');

instance1.books.push('test book');

console.log(instance1.books,instance2.books);
instance2.getName();
instance2.getTime();

Наследование таким образом на самом деле показано на рисунке выше.Самое большое изменение - это обработка в прототипе подкласса, на который дается ссылка в прототипе родительского класса.Это объект, поэтому вам нужно заплатить одну вещь Обратите внимание на то, что подкласс хочет добавить методы прототипа, которые должны быть добавлены через прототип. В противном случае присваивание непосредственно объекту перезапишет объект, унаследованный от прототипа родительского класса.

Принцип реализации класса ES6

О некотором базовом использовании и введении класса в ES6 из-за нехватки места в этой статье не будет. В этой главе мы в основномREPL БабеляДавайте посмотрим на некоторые реализации различных синтаксических сахаров, включая наследование в es6.

базовый класс

Мы будем следовать этому классу и тереться туда-сюда. Затем проанализируйте скомпилированный код.

"use strict";

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function Person(name) {
  _classCallCheck(this, Person);

  this.name = name;
};

_instanceofОн заключается в том, чтобы судить об отношениях между экземплярами. Приведенный выше код относительно прост,_classCallCheckпроверитьPersonЭтот класс, будь то черезnewназывается ключевое слово. Ведь после компиляции в ES5,functionМожно вызвать напрямую, но если вызвать напрямую,thisпросто укажите наwindowобъект, воляThrow Error.

добавить свойства

"use strict";

function _instanceof(left, right) {...}

function _classCallCheck(instance, Constructor) {...}

function _defineProperty(obj, key, value) {
    if (key in obj) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    } else {
        obj[key] = value;
    }
    return obj;
}

var Person = function Person(name) {
    _classCallCheck(this, Person);

    _defineProperty(this, "shili", '实例属性');

    this.name = name;
};

_defineProperty(Person, "jingtai", ' 静态属性');

По сути, речь идет о том, кому присваивается атрибут. Если это свойство экземпляра, назначьте его непосредственноthisВкл., если это статическое свойство, класс присвоения._definePropertyТо есть судить, повторяется ли имя атрибута.

добавить метод

"use strict";

function _instanceof(left, right) {...}

function _classCallCheck(instance, Constructor) {...}

function _defineProperty(obj, key, value) {...}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
}

var Person =
    /*#__PURE__*/
    function () {
        function Person(name) {
            _classCallCheck(this, Person);

            _defineProperty(this, "shili", '实例属性');

            this.name = name;
        }

        _createClass(Person, [{
            key: "sayName",
            value: function sayName() {
                return this.name;
            }
        }, {
            key: "name",
            get: function get() {
                return 'Nealyang';
            },
            set: function set(newName) {
                console.log('new name is :' + newName);
            }
        }], [{
            key: "eat",
            value: function eat() {
                return 'eat food';
            }
        }]);

        return Person;
    }();

_defineProperty(Person, "jingtai", ' 静态属性');

Кажется, что кода все еще много, но на самом деле это_createClassфункция и_definePropertiesтолько функция.

Первый взгляд_createClassТри параметра этой функции, первый — конструктор, второй — массив функций, которые нужно добавить в прототип, а третий — массив функций, добавленных в сам класс. На самом деле функция этой функции очень проста. этоУсильте конструктор, так называемый расширенный конструктор заключается в добавлении некоторых функций к конструктору или его прототипу.

и_definePropertiesбольше одного_defineProperty(Кажется, это бред, но это так). дефолтenumerableзаfalse,configurableзаtrue.

фактическиВыше приведен принцип реализации класса es6.

расширить ключевое слово

"use strict";

function _instanceof(left, right) {...}

function _classCallCheck(instance, Constructor) {...}

var Parent = function Parent(name) {...};

function _typeof(obj) {
    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
        _typeof = function _typeof(obj) {
            return typeof obj;
        };
    } else {
        _typeof = function _typeof(obj) {
            return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
        };
    }
    return _typeof(obj);
}

function _possibleConstructorReturn(self, call) {
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
    }
    return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
    if (self === void 0) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
}

function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            writable: true,
            configurable: true
        }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
    };
    return _setPrototypeOf(o, p);
}

var Child =
    /*#__PURE__*/
    function (_Parent) {
        _inherits(Child, _Parent);

        function Child(name, age) {
            var _this;

            _classCallCheck(this, Child);

            _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); // 调用父类的 constructor(name)

            _this.age = age;
            return _this;
        }

        return Child;
    }(Parent);

var child1 = new Child('全栈前端精选', '0.3');
console.log(child1);

Удалите генерацию кода, связанную с классом, а остальное — анализ синтаксического сахара наследования. вsuperКлючевое слово указывает на конструктор родительского класса, что эквивалентно ES5.Parent.call(this), а затем в соответствии с методом наследования, который мы упоминали выше, считаете ли вы, что реализация интеграции сказала намПаразитическая композиционная наследственностьочень похожий?

В классе ES6 подклассы должны быть вconstructorвызов методаsuperметод, иначе будет сообщено об ошибке при создании нового экземпляра. Это потому чтоПодклассы не имеют своихthisобъект, но наследует родительский классthisобъект, а затем обработать его. если не позвалиsuperметод, подклассы не получатthisобъект.

Также по этой причине в конструкторе подкласса только вызовsuperПосле этого вы можете использоватьthisключевое слово, иначе будет сообщено об ошибке.

Схематическая диаграмма цепочки прототипов в ES6 приведена на следующей диаграмме:

图片来自冴羽的博客

О ES6extendКлючевое слово, приведенный выше код мы можем видеть по исполнению. На самом деле код ключа представляет собой не что иное, как две строчки:

 _inherits(Child, _Parent);
  _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); 

Разберем конкретную реализацию отдельно:

_inherits

Код относительно прост, весь упомянутый выше контент, т.е.Установите цепочку отношений прототипа между потомком и родителем. Объяснение кода отмечено в коде

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {//subClass 类型判断
        throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {//Object.create 第二个参数是给subClass.prototype添加了 constructor 属性
            value: subClass,
            writable: true,
            configurable: true//注意这里enumerable没有指名,默认是 false,也就是说constructor为不可枚举的。
        }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
    };
    return _setPrototypeOf(o, p);
}

_possibleConstructorReturn

_this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));

Согласно диаграмме прототипа es6, которую мы разобрали на рисунке выше, мы видим, что:

Child.prototype === Parent

Таким образом, приведенный выше код мы можем преобразовать в:

_this = _possibleConstructorReturn(this, Parent.call(this, name));

Затем набираем реализацию исходного кода слой за слоем

function _possibleConstructorReturn(self, call) {
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
    }
    return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
    if (self === void 0) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
}

Вышеприведенный код,selfФактически, он возвращается Чайлдс IIFE.function newназываетсяthis, результат печати выглядит следующим образом:

может быть прямо здесьParent.call(this,name)Какие-то сомнения, не беда, под Хромом отлаживать можно.

можно увидеть, когда мыParentКонструктор пишется так

class Parent {
    constructor(name) {
        this.name = name;
    }
}

Затем, наконец, перейдите к_possibleConstructorReturnвторой параметр функцииcallтолько одинundefined. так в_possibleConstructorReturnфункция будетcallсделать суждение, вернуть правильныйthisнаправление:Child.

Таким образом, цель общего кодаОпределите начальное значение _this конструктора подкласса this на основе типа возвращаемого значения конструктора Parent..

Наконец

[ПОСЛЕДНИЙ РАЗ] Серия статей по основам JavaScript на данный момент пополнилась тремя статьями. В конце давайте еще один классический вопрос для интервью!

function Foo() {
  getName = function() {
    alert(1);
  };
  return this;
}
Foo.getName = function() {
  alert(2);
};
Foo.prototype.getName = function() {
  alert(3);
};
var getName = function() {
  alert(4);
};
function getName() {
  alert(5);
}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

Старое железо, оставляйте свои мысли в комментариях~

учеба по обмену

Обратите внимание на официальный аккаунт: [Полный выбор внешнего интерфейса] Время от времени получайте хорошие рекомендации статей и оригинальные статьи.

Ответьте на [1] в официальном аккаунте, присоединитесь к группе полного обучения интерфейсу и общайтесь вместе.

использованная литература