предисловие
JavaScript — это объектно-ориентированный язык, все объекты наследуют свойства и методы от прототипа, так что же такое прототип? Как добиться наследования между объектами?
Эта статья поможет вам глубже понять прототипы в JavaScript. Заинтересованные разработчики могут прочитать эту статью.
Принципиальный анализ
Далее давайте шаг за шагом проанализируем отношения между прототипом и объектом.
прототип объекта
Мы используемfunction
ключевое слово для создания функции, памятиprototype
Объект свойств, указывающих на функциюпрототип объекта,Следующим образом:
function Person() {
}
Person.prototype // {constructor: Person(), __proto__}
В приведенном выше коде мы создали функцию с именем Person:
- Свойство прототипа указывает на объект-прототип Person (каждый объект JS, кроме null, связан с другим объектом при его создании, и этот связанный объект является прототипом)
- Каждый объект «наследует» свойства от прототипа
- Объект-прототип содержит
constructor
а также__proto__
Атрибуты.
Давайте нарисуем картину, чтобы описатьPerson
а такжеprototype
Отношение между
Функция, вызываемая оператором new,Конструктор, рекомендуется писать имена конструкторов с заглавной буквы.
Связь между экземплярами функций и объектами-прототипами
В прошлой главе мы разъяснилиКонструктора такжепрототип объектаотношения, давайте посмотримЭкземпляр функцииа такжепрототип объектаОтношение между.
мы используем операторnew
Создано из предыдущей главыPerson
Создается экземпляр функции и получается экземпляр человека Код выглядит следующим образом:
// 实例化对象
const person = new Persion();
В прошлой главе мы знаем, что объект-прототип имеет 2 свойства, среди которых__proto__
— это свойство, которое есть у каждого объекта JavaScript, кроме null, и оно указывает на объект-прототип конструктора объекта.
Далее докажемperson.__proto__
ли иPersion.prototype
Равно, код выглядит следующим образом:
function Person() {
}
const person = new Persion();
console.log("函数实例的__proto__指针指向构造函数的原型对象: ", person.__proto__ === Person.prototype);
Результат выполнения следующий:
Помимо использования
__proto__
Чтобы получить доступ к объекту-прототипу, мы также можем использовать Object.getPrototypeOf() для его получения.
Доказав, что они равны, объедините конструктор и объект-прототип, чтобы узнать взаимосвязь между ними тремя, следующим образом:
Когда мы создаем экземпляр конструктора, конструктор также создается для этого экземпляра.
__proto__
свойство, это свойство является указателем на объект-прототип конструктора.Все объекты экземпляра, созданные одним и тем же конструктором
__proto__
Его свойства направлены на конструкторы объектов-прототипов, свойства и методы, поэтому все экземпляры объектов являются общими функциями конструктора объекта-прототипа, поэтому после изменения свойства или метода объекта-прототипа все объекты будут затронуты экземплярами.
Мы думаем о такой проблеме, так как каждый объект JS, кроме null, имеет__proto__
свойство, то Person также является объектом, который также содержит__proto__
собственность, так куда же она указывает?
Ответ очевиден, как мы сказали выше:__proto__
Указатель на прототип объекта конструктора.
Покажем пример:
function Person() {}
Person.__proto__ === Person.constructor.prototype // true
- В приведенном выше коде мы использовали
constructor
свойство, указывающее на конструктор объекта, который мы подробно объясним в следующей главе.
Результат выполнения следующий:
Связь между объектами-прототипами и конструкторами
В прошлой главе мы проанализировали объект-прототип__proto__
Направление, давайте проанализируем дальшеconstructor
указывает на. Каждый объект-прототип имеетconstructor
свойство, которое указывает на конструктор объекта.
Далее докажемPerson.prototype.constructor
ли иPerson
Равно, код выглядит следующим образом:
function Person() {
}
const person = new Person();
console.log("原型对象与构造函数相等: ", Person.prototype.constructor === Person);
Результат выполнения следующий:
Доказав, что они равны, мы объединяем конструктор, экземпляр функции и объект-прототип, чтобы узнать взаимосвязь между ними четырьмя, следующим образом:
Чтобы получить прототип объекта, помимо доступа к его прототипу, мы также можем использовать Object.getPrototypeOf() для его получения.
Порядок чтения свойств экземпляра
При чтении свойств в экземпляре, если он не найден, он будет искать свойства в прототипе объекта, если не найден, он найдет прототип прототипа, и он найдет самый верхний уровень.
Далее приведем пример, подтверждающий вышеприведенное утверждение:
function Person() {
}
Person.prototype.name = "原型上的name属性";
const person = new Person();
person.name = "实例上的name属性";
console.log(person.name) // 实例上的name属性
delete person.name;
console.log(person.name); // 原型上的name属性
delete Person.prototype.name;
console.log(person.name); // undefined
Давайте проанализируем приведенный выше пример:
- Добавлен атрибут имени в прототип
- Добавлен атрибут name для экземпляра
- На данный момент значением атрибута name является атрибут name экземпляра.
- Удален атрибут имени в экземплярах
- На этом этапе он будет искать атрибут имени в прототипе, поэтому значением является атрибут имени в прототипе.
- Убран атрибут имени в прототипе
- В этот момент он найдет значение имени прототипа прототипа.Прототип прототипа не имеет атрибута имени, поэтому возвращает undefined
В приведенном выше анализе мы не нашли атрибут name в прототипе прототипа Person, так что же является прототипом прототипа Person? Распечатаем его в консоли Google Chrome, как показано ниже:
Как показано в результате на приведенном выше рисунке, прототип прототипа Person является объектом, что доказывает, что прототип также является объектом, тогда мы можем создать его самым примитивным способом, код выглядит следующим образом:
const object = new Object();
object.name = "对象中的属性";
console.log(object.name); // 对象中的属性
console.log("object实例的__proto__指向Object的实例对象", object.__proto__ === Object.prototype);
console.log("Object的原型对象与构造函数相等", Object.prototype.constructor === Object);
Результат выполнения следующий:
После знания того, что прототип также является объектом, в сочетании с тем, что мы доказали выше, связь между ними следующая:
Сеть прототипов
Из предыдущего анализа мы знаем, что все основано на Object, так что же является прототипом Object? ответ нулевой
Проверяем его на консоли Google Chrome, и результат такой:
Объединяя вышеизложенное, их окончательное соотношение выглядит следующим образом:
Цепная структура, образованная оранжевыми линиями на рисунке,Сеть прототипов.
Перепишите объект-прототип
Когда мы реализуем какую-то функциональность, мы часто переписываем весь объект-прототип с литералом объекта, который содержит все свойства и методы.
Следующим образом:
Person.prototype = {
name: "神奇的程序员",
age: "20",
job: "web前端开发",
sayName: function () {
console.log(this.name);
}
}
- Направьте прототип Person на новый объект
- Прототип имеет три свойства и один метод
- Свойство конструктора не существует в объекте
Поскольку в переопределенном объекте нет свойства конструктора, его свойство конструктора будет указывать на Object.
Проверим, код такой:
console.log("Person的原型对象的构造函数与Person构造函数相等", Person.prototype.constructor === Person)
Результат выполнения следующий:
Если значение конструктора очень важно, то нужно намеренно изменить указатель конструктора на конструктор Код такой:
Person.prototype = {
name: "神奇的程序员",
age: "20",
job: "web前端开发",
sayName: function () {
console.log(this.name);
},
constructor: Person
}
console.log("Person的原型对象与Person构造函数相等", Person.prototype.constructor === Person)
Результат выполнения следующий:
Наследование цепочки прототипов
В предыдущей главе, посвященной принципиальному анализу, на итоговой схематической диаграмме мы можем интуитивно увидеть, как выглядит цепочка прототипов.Далее давайте рассмотрим конкретную концепцию цепочки прототипов.
- Каждый конструктор имеет прототип объекта
- Все объекты-прототипы содержат указатель на конструктор (
constructor
) - Каждый экземпляр конструктора содержит внутренний указатель на объект-прототип (
__proto__
) - Если объект-прототип сделать равным экземпляру другого конструктора, объект-прототип будет содержать указатель на прототип другого конструктора.
- Соответственно прототип другого конструктора также содержит указатель на другой конструктор
- Если другой прототип является экземпляром другого конструктора, указанное выше отношение остается в силе.
- Таким образом формируется цепочка экземпляров и прототипов, оранжевая линия, которую мы видим на схематической диаграмме Это основная концепция цепочки прототипов.
Далее мы используем пример для объяснения наследования цепочки прототипов.Код выглядит следующим образом:
function Super() {
this.property = true;
}
Super.prototype.getSuperValue = function() {
return this.property;
}
function Sub() {
this.subProperty = false;
}
// Sub原型指向Super实例,constructor被重写,指向Super
Sub.prototype = new Super();
Sub.prototype.getSubValue = function () {
return this.subProperty;
}
let sub = new Sub();
console.log("获取Super的属性值", sub.getSuperValue());
console.log("sub实例的原型对象等于Sub构造函数的原型对象", sub.__proto__ === Sub.prototype);
console.log("Sub构造函数的原型对象的原型对象等于Super构造函数的原型对象", Sub.prototype.__proto__ === Super.prototype)
console.log("Sub构造函数的原型对象constructor指向Super的构造函数", Sub.prototype.constructor === Super)
Результаты приведены ниже:
- Во-первых, мы создали функцию с именем Super и добавили внутрь свойство с именем property со значением true.
- Впоследствии на прототип объекта Super добавили
getSuperValue
метод, который возвращаетproperty
Атрибуты - Впоследствии мы создали функцию под названием Sub internal, добавив свойство с именем subProperty, значение равно false
- Затем мы указываем объект-прототип Sub на экземпляр Super.На этом этапе реализовано наследование, и прототип Sub будет иметь методы прототипа Super.
- Впоследствии мы добавили к объекту прототипа Sub
getSubValue
метод, который возвращаетsubProperty
Атрибуты - Наконец, мы создаем экземпляр объекта Sub, который образует цепочку прототипов с объектом Super, что соответствует отношениям, о которых мы говорили в основном анализе.
Затем мы нарисуем вышеуказанное содержание анализа на диаграмме, чтобы лучше понять его, как показано ниже (оранжевая линия — это цепочка прототипов):
существующие проблемы
Когда мы используем цепочку прототипов для реализации наследования, если мы наследуем ссылочный тип объекта-прототипа, то этот ссылочный тип будет общим для всех экземпляров.
Далее мы проиллюстрируем эту проблему на примере:
function Super() {
this.list = ["a","b","c"];
}
function Sub() {
}
Sub.prototype = new Super();
const sub1 = new Sub();
sub1.list.push("d");
console.log(sub1.list);
const sub2 = new Sub();
console.log(sub2.list);
В приведенном выше коде:
- Сначала объявляются два конструктора Super и sub.
- Затем укажите прототип объекта Sub на экземпляр Super для реализации наследования.
- Затем создайте экземпляр подобъекта, чтобы получить
sub1
пример - Добавить элемент d в список экземпляров sub1
- В этот момент элементы массива списка экземпляра sub1
[ 'a', 'b', 'c', 'd' ]
- Затем снова создайте экземпляр Sub объекта, чтобы получить
sub2
пример - В этот момент элементы массива списка экземпляра sub2
[ 'a', 'b', 'c', 'd' ]
Результаты приведены ниже:
Проблема очевидна, мы не добавляем элементы в массив списка sub2, мы хотим, чтобы его значение было определено на прототипе Super["a","b","c"]
.
Поскольку атрибут списка, определенный в конструкторе Super, является ссылочным типом, он используется совместно во время создания экземпляра.[ 'a', 'b', 'c', 'd' ]
В следующей главе мы рассмотрим проблему ссылочных типов, совместно используемых экземплярами.
Наследование конструктора
В конструкторе подкласса мы можем использоватьcall
Скопируйте все свойства и методы конструктора родительского класса в текущий конструктор, чтобы после создания экземпляра изменение свойств и методов было скопированным содержимым, которое не повлияет на содержимое в конструкторе родительского класса.
Далее, давайте проиллюстрируем приведенное выше утверждение на примере:
function Super() {
this.list = ["a","b","c"];
}
function Sub() {
Super.call(this)
}
const sub1 = new Sub();
sub1.list.push("d");
console.log("sub1" ,sub1.list);
const sub2 = new Sub();
console.log("sub2", sub2.list);
Приведенный выше код, пример предыдущего раздела, который мы следуем, где разговора с измененной частью:
-
Мы используем вызов в конструкторе Sub, чтобы скопировать свойства и методы в Super для реализации наследования.
-
Поскольку атрибуты копируются в текущий экземпляр каждый раз, когда они создаются, элементы, добавленные в sub1, не повлияют на sub2.
Результаты приведены ниже:
существующие проблемы
Мы используем конструктор для реализации наследования, и мы не можем наследовать методы и свойства на прототипе родительского класса, поэтому возможность повторного использования функции будет потеряна.
Продолжим в качестве примера взять код из предыдущей главы:
function Super() {
this.list = ["a","b","c"];
}
Super.prototype.newList = [];
function Sub() {
Super.call(this)
}
const sub1 = new Sub();
console.log("sub1" ,sub1.newList);
- Мы добавили в суперпрототип
newList
Атрибуты - не существует при доступе в экземпляре sub1
Результаты приведены ниже:
наследование композиции
После анализа первых двух глав мы знаем, что метод наследования цепочки прототипов может наследовать свойства и методы объекта-прототипа, а наследование конструктора может наследовать свойства и методы функции-конструктора, они дополняют друг друга, поэтому мы объединят свои силы. Вставай, дело сделанонаследование композиции, а также прекрасно компенсируют соответствующие им недостатки.
Далее мы используем пример для объяснения наследования композиции:
// 组合继承
function Super(name) {
this.name = name;
this.list = ["a","b","c"];
}
Super.prototype.getName = function () {
return this.name;
}
function Sub(name, age) {
// 构造函数继承,第二次调用父类构造函数
Super.call(this,name);
this.age = age;
}
// 原型链继承,第一次调用父类构造函数
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.getAge = function () {
return this.age;
}
const sub1 = new Sub("神奇的程序员","20");
sub1.list.push("d");
console.log("sub1", sub1.getName());
console.log("sub1", sub1.getAge());
console.log("sub1", sub1.list);
const sub2 = new Sub("大白","20");
console.log("sub2", sub2.getName());
console.log("sub2", sub2.getAge());
console.log("sub2", sub2.list);
В приведенном выше коде:
- Сначала создайте
Super
Функция принимает параметр имени, а в конструкторе есть два атрибута: имя и список. - Впоследствии мы
Super
добавить объект-прототипgetName
Метод, возвращающий значение атрибута name в Super - Затем мы создаем
Sub
Функция принимает два параметра: имя, возраст, добавляет атрибут возраста в конструктор и наследует атрибуты и методы в конструкторе родительского класса. - Затем мы переписываем прототип объекта конструктора Sub в экземпляр Super и модифицируем конструктор, чтобы он указывал на
- Впоследствии мы
Sub
добавить объект-прототипgetAge
Метод, возвращающий свойство age в Sub - Наконец, мы создаем два экземпляра конструктора Sub для проверки унаследованного метода.
Результаты приведены ниже:
наследование паразитарного состава
Когда мы реализуем составное наследование, мы дважды вызываем конструктор родительского класса.
При первом вызове конструктора родительского класса:
- Мы переписываем объект-прототип Sub, чтобы он указывал на экземпляр Super.
- На этом этапе свойства и методы экземпляра конструктора родительского класса назначаются
Sub.prototype
Этот звонок, где мы находимсяНаследование цепочки прототиповКак упоминалось в главе, подкласс унаследовал свойства и методы конструктора родительского класса, а также свойства и методы объекта-прототипа.
При вызове конструктора родительского класса во второй раз:
- Внутри конструктора Sub мы используем
call
будуSuper
Свойства и методы назначаются экземплярам Sub. - Когда цепочка прототипов ищет свойства, свойства в экземпляре блокируют свойства в цепочке прототипов.
Поэтому при втором вызове нам не нужно назначать свойства и методы в конструкторе экземпляру конструктора Sub, что бессмысленно.
Далее давайте посмотрим на оптимизированное наследование композиции:
function Super(name) {
this.name = name;
this.list = ["a", "b", "c"];
}
Super.prototype.getName = function () {
return this.name;
}
function Sub(name, age){
Super.call(this, name);
this.age = age;
}
// 创建一个中间函数,用于继承Super的原型对象
function F() {
}
// 将F的原型对象指向Super的原型对象
F.prototype = Super.prototype;
// 将Sub的原型对象指向F的实例
Sub.prototype = new F();
Sub.prototype.constructor = Sub;
Sub.prototype.getAge = function () {
return this.age;
}
const sub1 = new Sub("神奇的程序员","20");
sub1.list.push("d");
console.log("sub1", sub1.getName());
console.log("sub1", sub1.getAge());
console.log("sub1", sub1.list);
const sub2 = new Sub("大白","20");
console.log("sub2", sub2.getName());
console.log("sub2", sub2.getAge());
console.log("sub2", sub2.list);
В приведенном выше коде:
- Сначала мы создали промежуточную функцию
F
- Впоследствии объект-прототип F был переписан, и на объект-прототип F было прямо указано.
Super
прототип объекта - Наконец, мы указываем объект-прототип Sub на экземпляр F, тем самым реализуя наследование цепочки прототипов.
Его эффективность заключается в том, что конструктор Super вызывается только один раз при создании экземпляра, а цепочка прототипов остается неизменной.
Результаты приведены ниже:
Оптимизированное наследование композиции, также известное какнаследование паразитарного состава, в приведенном выше коде реализации мы используем промежуточную функцию для реализации наследования цепочки прототипов, эту промежуточную функцию также можно использовать
Object.create()
Вместо этого принципы их реализации одинаковы.Затем при переопределении объекта-прототипа конструктора Sub мы можем написать:
Sub.prototype = Object.create(Super.prototype, {constructor: {value: Sub}})
Изменить прототип объекта указывает на наследование реализации
В предыдущей главе мы реализовали наследование цепочки прототипов с помощью промежуточной функции. Мы также можем напрямую передать объект-прототип подкласса через__proto__
Атрибут указывает на объект-прототип родительского класса.Таким образом, объект-прототип подкласса не меняется, поэтомуconstructor
Свойство также указывает на конструктор родительского класса.
Далее, давайте проиллюстрируем приведенное выше утверждение на примере:
function Super(name) {
this.name = name;
this.list = ["a", "b", "c"];
}
Super.prototype.getName = function () {
return this.name;
}
function Sub(name, age){
Super.call(this, name);
this.age = age;
}
// 修改Sub构造函数的原型对象指向改为Super的原型对象
Sub.prototype.__proto__ = Super.prototype;
Sub.prototype.getAge = function () {
return this.age;
}
const sub1 = new Sub("神奇的程序员","20");
sub1.list.push("d");
console.log("sub1", sub1.getName());
console.log("sub1", sub1.getAge());
console.log("sub1", sub1.list);
const sub2 = new Sub("大白","20");
console.log("sub2", sub2.getName());
console.log("sub2", sub2.getAge());
console.log("sub2", sub2.list);
В приведенном выше коде:
- Мы используем
__proto__
Свойство изменяет объект прототипа Sub, чтобы он указывал на
существуетПринципиальный анализглава, мы знаем, что все, кромеnull
внешние объекты javascript имеют__proto__
Свойство по умолчанию указывает на объект-прототип этого объекта, поэтому мы можем изменить объект-прототип, на который он указывает через это свойство.
Мы также можем использовать ES6
Object.setPrototypeOf()
метод для изменения прототипа объекта.Затем код в приведенном выше примере может быть изменен на:
Object.setPrototypeOf(Sub.prototype, Super.prototype)
Статическое наследование методов для конструкторов
Добавьте метод непосредственно в конструктор, этот методстатический метод.
Все методы наследования, о которых мы говорили ранее, не реализовывали статическое наследование методов на конструкторе, а в ES6class
При наследовании подкласс может наследовать статические методы суперкласса.
мы можем пройтиObject.setPrototypeOf()
Методы реализуют наследование статических методов.
Далее объясним это на конкретном примере:
function Super(name) {
this.name = name;
this.list = ["a", "b", "c"];
}
Super.prototype.getName = function () {
return this.name;
}
// 添加静态方法
Super.staticFn = function () {
return "Super的静态方法";
}
function Sub(name, age) {
// 继承Super构造函数中的数据
Super.call(this, name);
this.age = age;
}
// 修改Sub的原型指向
Object.setPrototypeOf(Sub.prototype, Super.prototype);
// 继承父类的静态属性与方法
Object.setPrototypeOf(Sub, Super);
Sub.prototype.getAge = function () {
return this.age;
}
console.log(Sub.staticFn());
const sub1 = new Sub("神奇的程序员", "20");
sub1.list.push("d");
console.log("sub1", sub1.list);
console.log("sub1", sub1.getName());
console.log("sub1", sub1.getAge());
const sub2 = new Sub("大白", "20");
console.log("sub2", sub2.list);
console.log("sub2", sub2.getName());
console.log("sub2", sub2.getAge());
Результаты приведены ниже:
Остальная часть приведенного выше кода такая же, как и в предыдущем примере, давайте проанализируем различия:
- Сначала мы
Super
Конструктор добавляетstaticFn
статический метод - Затем мы проходим
Object.setPrototypeOf(Sub, Super)
функция наследуетSuper
Статические свойства и методы конструктора
На данный момент мы добились идеального наследования, которое также является базовой реализацией синтаксического сахара класса ES6.
синтаксический сахар класса
Новый был добавлен в ES6class
модификатор, мы используемclass
После того, как модификатор создаст два объекта, мы можем использоватьextends
ключевое слово для реализации наследования. Его основная реализация - это то, о чем мы говорили выше.наследование паразитарного составакомбинироватьСтатическое наследование методов для конструкторовбыть реализованным.
Далее, давайте посмотрим на пример, приведенный в предыдущей главе, как использовать класс для реализации, код выглядит следующим образом:
class Super {
constructor(name) {
this.name = name;
this.list = ["a","b","c"];
}
getName() {
return this.name;
}
}
// 向Super添加静态方法
Super.staticFn = function () {
return "Super的静态方法";
}
class Sub extends Super{
constructor(name, age) {
super(name);
this.age = age;
}
getAge() {
return this.age;
}
}
console.log(Sub.staticFn())
const sub1 = new Sub("神奇的程序员", "20");
sub1.list.push("d");
console.log("sub1", sub1.list);
console.log("sub1", sub1.getName());
console.log("sub1", sub1.getAge());
const sub2 = new Sub("大白", "20");
console.log("sub2", sub2.list);
console.log("sub2", sub2.getName());
console.log("sub2", sub2.getAge());
Результаты приведены ниже:
кодовый адрес
Весь пример кода из этой серии статей можно найти по адресу:js-learning
напиши в конце
Эта статья является второй статьей в серии "Изучение принципов JS". Пожалуйста, перейдите к полному маршруту этой серии:Изучение принципов JS (1) 》Планирование маршрута обучения
- Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
- Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌