предисловие
В предыдущей статье подробно анализировались связанные точки знаний о прототипе и цепочке прототипов.Эта статья посвящена наследованию, которое тесно связано с цепочкой прототипов.Это очень важная точка знаний в фундаменте интерфейса.Это очень важно для повторного использования кода. Полезно, в этой статье будут подробно проанализированы различные методы наследования, а также преимущества и недостатки 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);
Экземпляр подкласса цепочки прототипов
Прототип цепного наследования для получения свойств и методов родительского класса
-
fatherFnСвойства/методы, объявленные через this, привязаны кnewна новых объектах, созданных за это время. - Прототип нового объекта
father.prototype, найденный через свойства цепочки прототиповfather.prototypeсвойства и методы.
пониматьnewЧто вы наделали:
Новое появляется много раз в этой статье. Новое также является очень важной частью Фонда JS. Многие точки знаний будут включать в себя новые. Если вы этого не понимаете, вы должны прочитать его несколько раз.
- Создайте совершенно новый объект.
- Прототип этого нового объекта (
__proto__) указывает на функциюprototypeобъект. - Когда функция выполняется, this функции привязывается к вновь созданному объекту.
- Если функция не возвращает другие объекты (включая массивы, функции, объекты даты и т. д.), то автоматически возвращается этот новый объект.
- Возвращаемый объект является экземпляром конструктора.
Функция вызова конструкции возвращает другой объект
Возврат других объектов не приведет к получению экземпляра конструктора, что может легко вызвать непредвиденные проблемы.!
мы знаем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 = {
...
}
Недостатки наследования цепи прототипа
-
Родительский класс использует
thisОбъявленные свойства являются общими для всех экземпляровПричина в том, что экземпляр родительского класса (
sonFn.prototype = new fatherFn()) — это одноразовое присвоение прототипу экземпляра подкласса (sonFn.prototype), он будет передавать родительский класс черезthisЗаявленное имущество также закрепляется заsonFn.prototypeначальство.
Стоит отметить, что: во многих блогах говорится, что свойства ссылочных типов являются общими для всех экземпляров, обычно на примере массивов, на самом деле массивы и другие родительские классы передаются
thisОбъявленные свойства также просто переданыПоиск цепочки прототиповчтобы получить прототип экземпляра подкласса (sonFn.prototype) по стоимости.
- При создании экземпляра подкласса нельзя передавать параметры конструктору родительского класса, что недостаточно гибко.
Атрибуты и методы родительского класса этого режима определяются с самого начала и не могут передавать параметры родительскому классу, который недостаточно гибок.
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);
- использовать в подклассах
callвызвать родительский класс,fatherFnбудет выполнено немедленно и будетfatherFnфункция this указывает наsonFnизthis. - Поскольку функция выполняется, поэтому
fatherFnФункции, объявленные с помощью this, будутобъявитьsonFnизthisобъектВниз. - создать экземпляр подкласса, это будет указывать на
newВо время создания нового объекта верните новый объект. - правильно
fatherFn.prototypeНет операции и не может быть унаследовано.
Свойства этого объекта: объявлены подклассами и суперклассамиthisсвойство/метод, прототипом которого является
PS: Для получения более подробной информации о вызове/применении/привязке рекомендуется проверить мой блог:js — интервьюер хочет знать, насколько хорошо вы понимаете call, apply, bind? [Не смотреть сериал о сожалениях]
Плюсы и минусы заимствования наследования конструктора
преимущество:
- Параметры могут быть переданы в родительский класс
- Решено наследование цепочки прототипов: использование свойств родительского класса
thisОбъявленные свойства будут общими для всех рассматриваемых экземпляров.
недостаток:
- Вы можете наследовать от родительского класса только через
thisОбъявленное свойство/метод не может быть унаследовано от родительского классаprototypeсвойства/методы на . - Метод родительского класса нельзя использовать повторно: потому что родительский класс не может быть унаследован
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Объявленные свойства/методы копируются в свойства экземпляра подкласса и его прототипа, а причина также комментируется в коде:
- Наследование цепочки прототипов: родительский класс проходит
thisа такжеprototypeОбъявленные свойства/методы, унаследованные от подклассовprototypeначальство. - Заимствованное конструктивное наследование: родительский класс объявляет свойства и методы для наследования свойств экземпляра дочернего класса через this.
Плюсы и минусы композиционного наследования
преимущество:
Полное наследование (не непригодное для использования), решено:
- родительский класс через
thisПроблема объявления свойств/методов для совместного использования экземплярами подкласса (проблема наследования цепочки прототипов) Каждый экземпляр подкласса будет повторно инициализировать суперкласс черезthisОбъявленное свойство, экземпляр ищется по правилу цепочки прототипов, каждый раз - родительский класс через
prototypeПроблема в том, что объявленные свойства/методы нельзя наследовать (проблема заимствования конструкторов).
недостаток:
- Вызовите функцию родительского класса дважды (
new fatherFn()а такжеfatherFn.call(this)), что приводит к определенной потере производительности. - Поскольку родительский класс вызывается дважды, родительский класс проходит
thisОбъявленные свойства/методы генерируют две копии задачи. - Контекст цепочки прототипов теряется: свойства/методы, объявленные подклассом и суперклассом через прототип, все существуют в прототипе подкласса.
прототипное наследование (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)
Плюсы и минусы прототипного наследования:
Достоинства: хорошая совместимость, простейшее наследование объектов.
недостаток:
- потому что старый объект (
oldObj) является экземпляром объекта (newObj) прототип, несколько экземпляров совместно используют свойства унаследованного объекта, и существует возможность подделки. - Не могу передать параметры
Паразитическое наследование (инкапсулирует процесс наследования)
СоздаватьФункция, используемая только для инкапсуляции процесса наследования, которая каким-то образом внутренне улучшает объект., который, наконец, возвращает объект.
function createAnother(original){
var clone = cloneObject(original); // 继承一个对象 返回新函数
// do something 以某种方式来增强对象
clone.some = function(){}; // 方法
clone.obkoro1 = '封装继承过程'; // 属性
return clone; // 返回这个对象
}
Сценарии использования: Улучшения, сделанные фиксированным образом специально для объектов.
Паразитарное комбинированное наследование (вызов + паразитарная инкапсуляция)
Принцип наследования паразитарного состава:
- Используйте конструктор заимствования (
call)ПриходитьНаследовать свойства/методы, объявленные родительским классом, в этом - Установите прототип родительского класса в качестве прототипа прототипа подкласса с помощью функции паразитной инкапсуляции.Наследовать свойства/методы, объявленные прототипом родительского класса.
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.
Паразитическое композиционное наследование имеет следующие преимущества перед композиционным наследованием:
-
Вызовите родительский класс только один раз
fatherFnКонструктор. -
Избегайте создания ненужных избыточных свойств в прототипах подклассов.
-
Наследование прототипа родительского прототипа сохраняет неизменным контекст цепочки прототипов.
Прототип подкласса отличается только свойствами/методами, объявленными подклассом через прототип, и свойствами/методами прототипа родительского класса.
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 Наследование