прототип объекта
Всякий раз, когда создается новая функция, для этой функции создается новая функция в соответствии с определенным набором правил.prototype
свойство, это свойство указывает на объект-прототип функции. По умолчанию все объекты-прототипы автоматически получаютconstructor
(конструктор), это свойство указывает наprototype
Функция, в которой находится свойство.
function Person(){
}
Когда мы создаем экземпляр с помощью конструктора, для этого экземпляра также создается экземпляр__proto__
собственность, это__proto__
свойство является указателем на объект-прототип конструктора
let person = new Person();
person.__proto__ === Person.prototype // true
let person1 = new Person();
person1.__proto__ === Person.prototype // true
Все объекты экземпляра, созданные одним и тем же конструктором__proto__
Все свойства указывают на объект-прототип конструктора, поэтому все объекты-экземпляры будут иметь общие свойства и методы объекта-прототипа конструктора.Как только свойства или методы объекта-прототипа будут изменены, это повлияет на все объекты-экземпляры.
function Person(){
}
Person.prototype.name = "Luke";
Person.prototype.age = 18;
let person1 = new Person();
let person2 = new Person();
alert(person1.name) // "Luke"
alert(person2.name) // "Luke"
Person.prototype.name = "Jack";
alert(person1.name) // "Jack"
alert(person2.name) // "Jack"
Переопределить объект-прототип
Мы часто переписываем весь объект-прототип с литералом объекта, содержащим все свойства и методы, как показано в следующем примере.
function Person(){
}
Person.prototype = {
name : "Luke",
age : 18,
job : "Software Engineer",
sayName : function(){
alert(this.name)
}
}
В приведенном выше коде мы будемPerson.prototype
установить новый объект, который не имеетconstructor
свойства, что приводит кconstructor
свойство больше не указывает наPerson
, но указывает наObject
.
let friend = new Person();
alert(friend.constructor === Person); //false
alert(friend.constructor === Object); //true
еслиconstructor
Значение важно, мы можем намеренно установить его обратно на соответствующее значение, как это
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Luke",
age : 18,
job : "Software Engineer",
sayName : function(){
alert(this.name)
}
}
Цепочка прототипов и наследование цепочки прототипов
У каждого конструктора есть объект-прототип, а объект-прототип содержит указатель на конструктор (constructor
), а экземпляры содержат внутренний указатель на объект-прототип (__proto__
). Так что, если мы сделаем объект-прототип равным экземпляру другого типа? Очевидно, что объект-прототип в этот момент будет содержать указатель на другой прототип, и, соответственно, другой прототип также содержит указатель на другой конструктор. Если другой прототип является экземпляром другого конструктора, то вышеприведенная связь сохраняется, и так слой за слоем формируется цепочка экземпляров и прототипов. Это основная концепция так называемой цепочки прототипов.
function Super(){
this.property = true;
}
Super.prototype.getSuperValue = function(){
return this.property;
}
function Sub(){
this.subproperty = false;
}
Sub.prototype = new Super(); //继承了 Super
Sub.prototype.getSubValue = function (){
return this.subproperty;
}
let instance = new Sub();
console.log(instance.getSuperValue()); //true
console.log(instance.__proto__ === Sub.prototype); //true
console.log(Sub.prototype.__proto__ === Super.prototype); //true
в коде вышеSub.prototype = new Super();
Создав экземпляр Super и назначив этот экземплярSub.prototype
реализовать наследование. Все свойства и методы, которые существуют в объектах экземпляра и прототипа Super в настоящее время, также существуют в Sub.prototype. экземпляр__proto__
Свойство указывает на объект-прототип SubSub.prototype
, объект подпрототипа__proto__
Свойство, в свою очередь, указывает на объект-прототип SuperSuper.prototype
.
Механизм поиска прототипа цепочки
При доступе к свойству экземпляра сначала выполняется поиск этого свойства. Если свойство не найдено, продолжается поиск прототипа экземпляра. В случае наследования по цепочке прототипов процесс поиска может продолжаться вверх по цепочке прототипов до тех пор, пока не будет найдено свойство или не будет выполнен поиск по цепочке прототипов самого высокого уровня.Object.prototype
, если не найдено, вернутьundefined
. Взяв пример выше, вызовinstance.getSuperValue()
Он пройдет через три шага поиска: 1) поиск экземпляров, 2) поиск Sub.prototype, 3) поиск Super.prototype, и метод будет найден на последнем шаге. В случае, когда свойство или метод не найдены, процесс поиска всегда проходит цикл за этапом до конца цепочки прототипов перед остановкой.
Проблема цепочки прототипов
Самая большая проблема с наследованием цепочки прототипов связана с прототипами, которые содержат значения ссылочного типа. Свойства прототипа значений ссылочного типа являются общими для всех экземпляров. И именно поэтому вы определяете свойства в конструкторе, а не в объекте-прототипе. Когда наследование достигается через прототип, прототип фактически является экземпляром другого типа. В результате исходные свойства экземпляра естественным образом стали текущими свойствами прототипа.
function Super(){
this.colors = ["red", "blue", "green"];
}
function Sub(){
}
Sub.prototype = new Super(); // 继承了Super
let instance1 = new Sub();
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
let instance2 = new Sub();
alert(instance2.colors); //"red, blue, green, black"
В приведенном выше коде конструктор Super определяет свойство colors, которое представляет собой массив. Каждый экземпляр Super будет иметь собственное свойство colors, содержащее собственный массив. Когда Sub наследует Super через цепочку прототипов, Sub.prototype становится экземпляром Super, поэтому он также имеет собственное свойство цветов. В результате все экземпляры Sub будут совместно использовать свойство colors. Вторая проблема с цепочкой прототипов заключается в том, что невозможно передать аргументы конструктору суперкласса, не затрагивая все экземпляры объекта.
Наследование конструктора (классическое наследование)
То есть при вызове конструктора родительского класса в конструкторе подкласса, когда создается экземпляр подкласса, этот экземпляр также будет иметь свойства и методы экземпляра родительского класса.
function Super(){
this.colors = ["red", "blue", "green"];
}
function Sub(){
Super.call(this, name); //继承了Super
}
let instance1 = new Sub();
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
let instance2 = new Sub();
alert(instance2.colors); //"red, blue, green"
Приведенный выше код при создании экземпляра Sub также вызовет конструктор Super, который выполнит весь код инициализации объекта, определенный в функции Super() для нового объекта Sub. В результате каждый экземпляр Sub будет иметь собственную копию свойства colors.
Проблема наследования конструктора
Если просто позаимствовать конструктор, то проблем с паттерном конструктора не избежать — методы определены в конструкторе, так что возможности использовать функцию нет. Кроме того, методы, определенные в прототипе суперкласса, невидимы для подкласса.
наследование композиции
Это относится к шаблону наследования, который сочетает в себе цепочку прототипов и конструктор, чтобы в полной мере использовать их сильные стороны. Идея состоит в том, чтобы использовать цепочку прототипов для реализации наследования свойств и методов прототипа, а также для реализации наследования свойств экземпляра путем заимствования конструкторов. Таким образом, повторное использование функций достигается путем определения методов в прототипе, и можно гарантировать, что каждый экземпляр будет иметь свои собственные свойства.
function Super(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function (){
alert(this.name);
};
function Sub(name, age){
Super.call(this, name); //继承了Super 属性 (第二次调用Sup构造函数)
this.age = age;
}
Sub.prototype = new Super(); // 继承了Super 原型链上的方法 (第一次调用Sup构造函数)
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function (){
alert(this.age);
};
var instance1 = new Sub("Luke", 18);
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
instance1.sayName(); //"Luke"
instance1.sayAge() //18
var instance2 = new Sub("Jack", 20);
alert(instance2.colors); //"red, blue, green"
instance2.sayName(); //"Jack"
instance2.sayAge() //20
В приведенном выше примере конструктор Sup определяет два свойства: имя и цвета. Прототип Sup определяет метод sayName(). Конструктор Sub передает параметр имени при вызове конструктора Sup, а затем определяет возраст своего собственного свойства. Затем экземпляр Sup назначается прототипу Sub, а метод sayAge() определяется для нового прототипа. Это позволяет двум разным экземплярам Sub иметь свои собственные свойства, включая свойство colors, и использовать один и тот же метод. Комбинаторное наследование позволяет избежать недостатков цепочек прототипов и конструкторов, сочетает в себе их преимущества и является наиболее часто используемым шаблоном наследования в JavaScript. Но ложка дегтя в том, что в приведенном выше коде конструктор родительского класса вызывается дважды. Sub.prototype = new Super(); При первом вызове конструктора родительского класса экземпляр конструктора родительского класса Sup присваивается объекту-прототипу Sub.prototype подкласса Sub. В это время свойства экземпляра конструктора родительского класса также назначаются объекту-прототипу Sub.prototype подкласса. Второй раз — вызвать конструктор родительского класса Super.call(this) в конструкторе подкласса, в этот раз свойства экземпляра конструктора родительского класса будут присвоены экземпляру конструктора подкласса. В соответствии с принципом поиска по цепочке прототипов свойства в экземпляре будут блокировать свойства в цепочке прототипов. Следовательно, нет необходимости назначать свойства экземпляра конструктора родительского класса объекту-прототипу дочернего класса, что является пустой тратой ресурсов и бессмысленным поведением.
Оптимизированное наследование композиции
function Super(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function (){
alert(this.name);
};
function Sub(name, age){
Super.call(this, name); //继承了Super 属性
this.age = age;
}
function F(){
}
F.prototype = Super.prototype;
Sub.prototype = new F(); // 继承了Super 原型链上的方法
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function (){
alert(this.age);
};
var instance1 = new Sub("Luke", 18);
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
instance1.sayName(); //"Luke"
instance1.sayAge() //18
var instance2 = new Sub("Jack", 20);
alert(instance2.colors); //"red, blue, green"
instance2.sayName(); //"Jack"
instance2.sayAge() //20
В приведенном выше примере завершается наследование цепочки прототипов путем прямого назначения объекта-прототипа родительского класса объекту-прототипу промежуточного конструктора, а затем присвоению экземпляра промежуточного конструктора объекту-прототипу Sub.prototype подкласса. Его эффективность выражается в том, что вызывается только один конструктор родительского класса Super, а цепочка прототипов остается неизменной. Другой удобный способ написания — использовать метод Object.create() из ES5 для замены промежуточного конструктора, на самом деле принцип тот же.
function Super(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function (){
alert(this.name);
};
function Sub(name, age){
Super.call(this, name); //继承了Super 属性
this.age = age;
}
/*
function F(){
}
F.prototype = Super.prototype;
Sub.prototype = new F(); // 继承了Super 原型链上的方法
Sub.prototype.constructor = Sub;
*/
//这行代码的原理与上面注释的代码是一样的
Sub.prototype = Object.create(Super.prototype, {constructor: {value: Sub}})
Sub.prototype.sayAge = function (){
alert(this.age);
};
var instance1 = new Sub("Luke", 18);
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
instance1.sayName(); //"Luke"
instance1.sayAge() //18
var instance2 = new Sub("Jack", 20);
alert(instance2.colors); //"red, blue, green"
instance2.sayName(); //"Jack"
instance2.sayAge() //20
Более простой способ наследования
Существует также более простой метод наследования, заключающийся в прямой передаче объекта-прототипа (прототипа) подкласса в__proto__
Указывая на объект-прототип родительского класса (прототип), этот метод не изменяет объект-прототип подкласса, поэтому объект-прототип подклассаconstructor
Свойство по-прежнему указывает на конструктор подкласса, и когда экземпляр подкласса не ищет соответствующее свойство или метод в объекте-прототипе подкласса, он передаст метод объекту-прототипу подкласса.__proto__
свойства, продолжайте поиск соответствующего свойства или метода в объекте-прототипе родительского класса.
function Super(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function (){
alert(this.name);
};
function Sub(name, age){
Super.call(this, name); //继承了Super 属性
this.age = age;
}
Sub.prototype.__proto__ = Super.prototype
Sub.prototype.sayAge = function (){
alert(this.age);
};
var instance1 = new Sub("Luke", 18);
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
instance1.sayName(); //"Luke"
instance1.sayAge() //18
var instance2 = new Sub("Jack", 20);
alert(instance2.colors); //"red, blue, green"
instance2.sayName(); //"Jack"
instance2.sayAge() //20
Object.setPrototypeOf()
Object.setPrototypeOf() — это метод в последней версии ECMAScript 6, в отличие от Object.prototype.proto, что считается более подходящим способом изменения прототипа объекта.
function Super(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function (){
alert(this.name);
};
function Sub(name, age){
Super.call(this, name); //继承了Super 属性
this.age = age;
}
//Sub.prototype.__proto__ = Super.prototype
Object.setPrototypeOf(Sub.prototype, Super.prototype)
Sub.prototype.sayAge = function (){
alert(this.age);
};
var instance1 = new Sub("Luke", 18);
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
instance1.sayName(); //"Luke"
instance1.sayAge() //18
var instance2 = new Sub("Jack", 20);
alert(instance2.colors); //"red, blue, green"
instance2.sayName(); //"Jack"
instance2.sayAge() //20
наследование статического метода класса
Все вышеперечисленные методы наследования не реализуют статическое наследование методов классов, а в ES6class
При наследовании подкласс может наследовать статические методы суперкласса. мы можем пройтиObject.setPrototypeOf()
Реализовать статическое наследование методов классов очень просто
Object.setPrototypeOf(Sub, Super)
function Super(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function (){
alert(this.name);
};
Super.staticFn = function(){
alert('Super.staticFn')
}
function Sub(name, age){
Super.call(this, name); //继承了Super 属性
this.age = age;
}
//Sub.prototype.__proto__ = Super.prototype
Object.setPrototypeOf(Sub.prototype, Super.prototype)
Object.setPrototypeOf(Sub, Super) // 继承父类的静态属性或方法
Sub.staticFn() // "Super.staticFn"
Sub.prototype.sayAge = function (){
alert(this.age);
};
var instance1 = new Sub("Luke", 18);
instance1.colors.push("black");
alert(instance1.colors); //"red, blue, green, black"
instance1.sayName(); //"Luke"
instance1.sayAge() //18
var instance2 = new Sub("Jack", 20);
alert(instance2.colors); //"red, blue, green"
instance2.sayName(); //"Jack"
instance2.sayAge() //20
Это, вероятно, окончательное идеальное наследование.