Основы JS - идеальное понимание точек знаний о наследовании

JavaScript
Основы JS - идеальное понимание точек знаний о наследовании

предисловие

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

Эта статья требует понимания прототипов, цепочек прототипов иcall сопутствующие знания:

Основы JS — связь между функциями, объектами и прототипами, цепочки прототипов

js — интервьюер хочет знать, насколько хорошо вы понимаете call, apply, bind?

Что такое наследование?

Википедия: Наследование позволяет подклассам иметь различные свойства и методы родительского класса без необходимости повторного написания одного и того же кода.

Наследование — это процесс, посредством которого класс приобретает методы и свойства другого класса..

PS: или несколько классов

JS реализует принцип наследования

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

Скопируйте свойства и методы родительского класса, чтобы переопределить объект-прототип дочернего класса..

Наследование цепочки прототипов (новое):

function fatherFn() {
  this.some = '父类的this属性';
}
fatherFn.prototype.fatherFnSome =  '父类原型对象的属性或者方法';
// 子类
function sonFn() {
  this.obkoro1 = '子类的this属性';
}
// 核心步骤:重写子类的原型对象
sonFn.prototype = new fatherFn(); // 将fatherFn的实例赋值给sonFn的prototype
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法' // 子类的属性/方法声明在后面,避免被覆盖
// 实例化子类
const sonFnInstance = new sonFn();
console.log('子类的实例:', sonFnInstance);

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

原型链子类实例

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

  1. fatherFnСвойства/методы, объявленные через this, привязаны кnewна новых объектах, созданных за это время.
  2. Прототип нового объектаfather.prototype, найденный через свойства цепочки прототиповfather.prototypeсвойства и методы.

пониматьnewЧто вы наделали:

Новое появляется много раз в этой статье. Новое также является очень важной частью Фонда JS. Многие точки знаний будут включать в себя новые. Если вы этого не понимаете, вы должны прочитать его несколько раз.

  1. Создайте совершенно новый объект.
  2. Прототип этого нового объекта (__proto__) указывает на функциюprototypeобъект.
  3. Когда функция выполняется, this функции привязывается к вновь созданному объекту.
  4. Если функция не возвращает другие объекты (включая массивы, функции, объекты даты и т. д.), то автоматически возвращается этот новый объект.
  5. Возвращаемый объект является экземпляром конструктора.

Функция вызова конструкции возвращает другой объект

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

мы знаемfatherFnизthisа такжеprototypeСвойства/методы такие же, какnewновые объекты, созданные во время.

Если в родительском классе возвращаются другие объекты (newчетвертый пункт), остальные объекты не имеют родительского классаthisа такжеprototype, что приводит к сбою наследования цепочки прототипов.

Протестируем, модифицируем родительский класс в наследовании цепочки прототиповfatherFn:

function fatherFn() {
  this.some = '父类的this属性';
  console.log('new fatherFn 期间生成的对象', this)
  return [ '数组对象', '函数对象', '日期对象', '正则对象', '等等等', '都不会返回new期间创建的新对象' ]
}

原型链继承返回其他对象,将导致原型链继承失败

PS: Ни одна из функций вызова конструктора в этой статье не может возвращать другие функции, и этот момент не будет упоминаться ниже.

Не используйте литералы объектов для создания методов-прототипов:

Таким образом, легко непреднамеренно очистить/переписать исходные свойства/методы объекта-прототипа.Этот способ записи не следует использовать для простоты.

Некоторые люди, которым необходимо создать несколько свойств и методов в объекте-прототипе, используют литералы объектов для их создания:

sonFn.prototype = new fatherFn();
// 子类的prototype被清空后 重新赋值, 导致上一行代码失效
sonFn.prototype = {
    sonFnSome: '子类原型对象的属性',
    one: function() {},
    two: function() {},
    three: function() {}
}

Существует также обычная практика, в результате которой свойства объекта-прототипа функцииconstructorотсутствующий:

function test() {}
test.prototype = {
    ...
}

Недостатки наследования цепи прототипа

  1. Родительский класс используетthisОбъявленные свойства являются общими для всех экземпляров

    Причина в том, что экземпляр родительского класса (sonFn.prototype = new fatherFn()) — это одноразовое присвоение прототипу экземпляра подкласса (sonFn.prototype), он будет передавать родительский класс черезthisЗаявленное имущество также закрепляется заsonFn.prototypeначальство.

Стоит отметить, что: во многих блогах говорится, что свойства ссылочных типов являются общими для всех экземпляров, обычно на примере массивов, на самом деле массивы и другие родительские классы передаютсяthisОбъявленные свойства также просто переданыПоиск цепочки прототиповчтобы получить прототип экземпляра подкласса (sonFn.prototype) по стоимости.

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

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

sonFn.prototype = new fatherFn()

Заимствование наследования конструктора (вызов)

 function fatherFn(...arr) {
  this.some = '父类的this属性';
  this.params = arr // 父类的参数
}
fatherFn.prototype.fatherFnSome = '父类原型对象的属性或者方法';
function sonFn(fatherParams, ...sonParams) {
  fatherFn.call(this, ...fatherParams); // 核心步骤: 将fatherFn的this指向sonFn的this对象上
  this.obkoro1 = '子类的this属性';
  this.sonParams = sonParams; // 子类的参数
}
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法'
let fatherParamsArr = ['父类的参数1', '父类的参数2']
let sonParamsArr = ['子类的参数1', '子类的参数2']
const sonFnInstance = new sonFn(fatherParamsArr, ...sonParamsArr); // 实例化子类
console.log('借用构造函数子类实例', sonFnInstance)

Экземпляр подкласса, унаследованный заимствующим конструктором

借用构造函数继承的子类实例

Что делает наследование заимствующего конструктора?

Объявить классы, организовать параметры и т.д., просто код вспомогательного контекста, ядром является заимствование конструкторов для использованияcallЧто вы наделали:

Однажды позвонилcall/applyОни немедленно выполняют функцию и изменяют ееthisнаправление

fatherFn.call(this, ...fatherParams); 
  1. использовать в подклассахcallвызвать родительский класс,fatherFnбудет выполнено немедленно и будетfatherFnфункция this указывает наsonFnизthis.
  2. Поскольку функция выполняется, поэтомуfatherFnФункции, объявленные с помощью this, будутобъявитьsonFnизthisобъектВниз.
  3. создать экземпляр подкласса, это будет указывать наnewВо время создания нового объекта верните новый объект.
  4. правильноfatherFn.prototypeНет операции и не может быть унаследовано.

Свойства этого объекта: объявлены подклассами и суперклассамиthisсвойство/метод, прототипом которого является

PS: Для получения более подробной информации о вызове/применении/привязке рекомендуется проверить мой блог:js — интервьюер хочет знать, насколько хорошо вы понимаете call, apply, bind? [Не смотреть сериал о сожалениях]

Плюсы и минусы заимствования наследования конструктора

преимущество:

  1. Параметры могут быть переданы в родительский класс
  2. Решено наследование цепочки прототипов: использование свойств родительского классаthisОбъявленные свойства будут общими для всех рассматриваемых экземпляров.

недостаток:

  1. Вы можете наследовать от родительского класса только черезthisОбъявленное свойство/метод не может быть унаследовано от родительского классаprototypeсвойства/методы на .
  2. Метод родительского класса нельзя использовать повторно: потому что родительский класс не может быть унаследованprototype, поэтому каждый раз, когда создается экземпляр подкласса, выполняется функция родительского класса, и родительский класс повторно объявляетсяthisМетод определен в , поэтому его нельзя использовать повторно.

Комбинированное наследование (вызов+новый)

принцип: использовать наследование цепочки прототипов (new)Будуthisа такжеprototypeОбъявленные свойства/методы, унаследованные от подклассовprototypeвыше, используйте заимствованный конструктор, чтобы наследовать родительский класс черезthisОбъявите свойства и методы для свойств экземпляров подкласса.

function fatherFn(...arr) {
  this.some = '父类的this属性';
  this.params = arr // 父类的参数
}
fatherFn.prototype.fatherFnSome = '父类原型对象的属性或者方法';
function sonFn() {
  fatherFn.call(this, '借用构造继承', '第二次调用'); // 借用构造继承: 继承父类通过this声明属性和方法至子类实例的属性上
  this.obkoro1 = '子类的this属性';
}
sonFn.prototype = new fatherFn('原型链继承', '第一次调用'); // 原型链继承: 将`this`和`prototype`声明的属性/方法继承至子类的`prototype`上
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法'
const sonFnInstance = new sonFn();
console.log('组合继承子类实例', sonFnInstance)

Объединение унаследованных экземпляров подкласса

组合继承的子类实例

Как видно из рисункаfatherFnпройти черезthisОбъявленные свойства/методы копируются в свойства экземпляра подкласса и его прототипа, а причина также комментируется в коде:

  1. Наследование цепочки прототипов: родительский класс проходитthisа такжеprototypeОбъявленные свойства/методы, унаследованные от подклассовprototypeначальство.
  2. Заимствованное конструктивное наследование: родительский класс объявляет свойства и методы для наследования свойств экземпляра дочернего класса через this.

Плюсы и минусы композиционного наследования

преимущество:

Полное наследование (не непригодное для использования), решено:

  1. родительский класс черезthisПроблема объявления свойств/методов для совместного использования экземплярами подкласса (проблема наследования цепочки прототипов) Каждый экземпляр подкласса будет повторно инициализировать суперкласс черезthisОбъявленное свойство, экземпляр ищется по правилу цепочки прототипов, каждый раз
  2. родительский класс черезprototypeПроблема в том, что объявленные свойства/методы нельзя наследовать (проблема заимствования конструкторов).

недостаток:

  1. Вызовите функцию родительского класса дважды (new fatherFn()а такжеfatherFn.call(this)), что приводит к определенной потере производительности.
  2. Поскольку родительский класс вызывается дважды, родительский класс проходитthisОбъявленные свойства/методы генерируют две копии задачи.
  3. Контекст цепочки прототипов теряется: свойства/методы, объявленные подклассом и суперклассом через прототип, все существуют в прототипе подкласса.

прототипное наследование (Object.create())

Наследование прототипа объекта — реализация Object.create()

Ниже приведеныObject.create()Макетная реализация с использованиемObject.create()Такого же эффекта можно добиться, в основном теперь используяObject.create()делать прототипное наследование объектов.

function cloneObject(obj){
  function F(){}
  F.prototype = obj; // 将被继承的对象作为空函数的prototype
  return new F(); // 返回new期间创建的新对象,此对象的原型为被继承的对象, 通过原型链查找可以拿到被继承对象的属性
}

ПС: вышеObject.create()Вы можете запомнить принцип реализации, и некоторые компании могут попросить вас рассказать о его принципе реализации.

пример:

let oldObj = { p: 1 };
let newObj = cloneObject(oldObj)
oldObj.p = 2
console.log('oldObj newObj', oldObj, newObj)

原型式继承

Плюсы и минусы прототипного наследования:

Достоинства: хорошая совместимость, простейшее наследование объектов.

недостаток:

  1. потому что старый объект (oldObj) является экземпляром объекта (newObj) прототип, несколько экземпляров совместно используют свойства унаследованного объекта, и существует возможность подделки.
  2. Не могу передать параметры

Паразитическое наследование (инкапсулирует процесс наследования)

СоздаватьФункция, используемая только для инкапсуляции процесса наследования, которая каким-то образом внутренне улучшает объект., который, наконец, возвращает объект.

function createAnother(original){
  var clone = cloneObject(original); // 继承一个对象 返回新函数
  // do something 以某种方式来增强对象
  clone.some = function(){}; // 方法
  clone.obkoro1 = '封装继承过程'; // 属性
  return clone; // 返回这个对象
}

Сценарии использования: Улучшения, сделанные фиксированным образом специально для объектов.

Паразитарное комбинированное наследование (вызов + паразитарная инкапсуляция)

Принцип наследования паразитарного состава:

  1. Используйте конструктор заимствования (call)ПриходитьНаследовать свойства/методы, объявленные родительским классом, в этом
  2. Установите прототип родительского класса в качестве прототипа прототипа подкласса с помощью функции паразитной инкапсуляции.Наследовать свойства/методы, объявленные прототипом родительского класса.
function fatherFn(...arr) {
  this.some = '父类的this属性';
  this.params = arr // 父类的参数
}
fatherFn.prototype.fatherFnSome = '父类原型对象的属性或者方法';
function sonFn() {
  fatherFn.call(this, '借用构造继承'); // 核心1 借用构造继承: 继承父类通过this声明属性和方法至子类实例的属性上
  this.obkoro1 = '子类的this属性';
}
// 核心2 寄生式继承:封装了son.prototype对象原型式继承father.prototype的过程,并且增强了传入的对象。
function inheritPrototype(son, father) {
  const fatherFnPrototype = Object.create(father.prototype); // 原型式继承:浅拷贝father.prototype对象 father.prototype为新对象的原型
  son.prototype = fatherFnPrototype; // 设置father.prototype为son.prototype的原型
  son.prototype.constructor = son; // 修正constructor 指向
}
inheritPrototype(sonFn, fatherFn)
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法'
const sonFnInstance = new sonFn();
console.log('寄生组合式继承子类实例', sonFnInstance)

Экземпляр наследования паразитарного состава

寄生组合式继承子类实例

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

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

Паразитическое композиционное наследование имеет следующие преимущества перед композиционным наследованием:

  1. Вызовите родительский класс только один разfatherFnКонструктор.

  2. Избегайте создания ненужных избыточных свойств в прототипах подклассов.

  3. Наследование прототипа родительского прототипа сохраняет неизменным контекст цепочки прототипов.

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

ES6 расширяет наследование:

Принцип наследования ES6 такой же, как и у наследования паразитарной композиции.

ES6 extendsОсновной код:

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

// 寄生式继承 封装继承过程
function _inherits(son, father) {
  // 原型式继承: 设置father.prototype为son.prototype的原型 用于继承father.prototype的属性/方法
  son.prototype = Object.create(father && father.prototype);
  son.prototype.constructor = son; // 修正constructor 指向
  // 将父类设置为子类的原型 用于继承父类的静态属性/方法(father.some)
  if (father) {
    Object.setPrototypeOf
      ? Object.setPrototypeOf(son, father)
      : son.__proto__ = father;
  }
}

Кроме того, подклассы наследуются путем заимствования конструктора (call), чтобы наследовать родительский класс черезthisОбъявленные свойства/методы также аналогичны паразитному композиционному наследованию.

Разница между наследованием ES5 и наследованием ES6:

Этот абзац взят изВводная документация Ruan Yifeng-es6

  • Наследование ES5 по существуСначала создайте экземпляр объекта подкласса, а затем добавьте к нему метод родительского класса..

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

    Поскольку у подкласса нет собственного объекта this, сначала должен быть вызван метод super() суперкласса.

Расширение:

Зачем исправлять конструктор, указывающий?

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

son.prototype.constructor = son; // 修正constructor 指向

Роль конструктора

MDNОпределение:Возвращает созданный объект экземпляраObjectссылка на конструктор.

То есть ссылка на конструктор, возвращающий экземпляр объекта, например:

let instance = new sonFn()
instance.constructor // sonFn函数

constructorСценарии применения:

Когда у нас есть только экземпляр объекта без ссылки на конструктор:

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

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

let instance = new sonFn() // 实例化子类
export instance;
// 多轮导入+导出,导致sonFn追踪非常麻烦,或者不想在文件中再引入sonFn
let  fn = instance.constructor
// do something: new fn() / fn.prototype / fn.length / fn.arguments等等

ДержатьconstructorУкажите на согласованность:

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

резюме

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

Рекомендуется прочитать эту статью несколько раз и построитьhtmlФайл попробуйте примеры в тексте, сочетание двух лучше!

Для студентов, которые не очень хорошо понимают прототип, вы можете посмотреть еще раз:Основы JS — связь между функциями, объектами и прототипами, цепочки прототипов

Если вы считаете, что мой блог полезен для вас, просто напишите мнеStarбар~

Передовое расширенное накопление,Нет публики,GitHub, wx:OBkoro1, почтовый ящик:obkoro1@foxmail.com

Выше 22 сентября 2019 г.

автор:OBKoro1

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

JS Advanced Programming (Красная книга) 6.3 Наследование

Восемь распространенных схем наследования в JavaScript