Сеть прототипов
Цепочка прототипов всегда вызывала недоумение в JS, но ее часто задают на собеседованиях. Здесь я подведу итоги. Во-первых, представим диаграмму отношений:
1. Чтобы понять цепочку прототипов, вы можете начать с рисунка выше, на котором есть три концепции:
1. Конструктор: Все функции в JS могут использоваться как конструкторы при условии, что они управляются оператором new;
function Parent(){
this.name = 'parent';
}
//这是一个JS函数
var parent1 = new Parent()
//这里函数被new操作符操作了,所以我们称Parent为一个构造函数;
2. Пример: parent1 получает новый Parent(), parent1 можно назвать экземпляром;
3. Объект-прототип: У конструктора есть свойство прототипа, которое инициализирует объект-прототип;
2. После разъяснения этих трех понятий, давайте поговорим о взаимосвязи между этими тремя понятиями (см. рисунок выше):
1. Применив оператор new к функции JS, получается экземпляр;
2. Конструктор инициализирует прототип, а этот прототип инициализирует объект-прототип, так как же объект-прототип узнает, какой функцией он инициализирован? Получается, что у объекта-прототипа будет свойство конструктора, указывающее на конструктор;
3. Тогда ключ в том, как объект-экземпляр связан с объектом-прототипом? Исходный объект экземпляра будет иметь атрибут __proto__, указывающий на объект-прототип, соответствующий конструктору объекта экземпляра;
4. Если мы ищем имя атрибута из объекта, если оно не найдено в текущем объекте, то мы будем продолжать поиск по атрибуту __proto__, пока не будет найден объект Object и атрибут name не будет найден, это доказывается, что имя атрибута не существует, иначе пока оно найдено, то это свойство существует.Из этого видно, что связь между JS-объектом и вышестоящим похожа на цепочку, которая называется цепь прототипов;
5. Если вы видите, что не поняли здесь цепочку прототипов, вы можете понять это из следующего. Я буду говорить о наследовании, потому что наследование прототипов основано на цепочке прототипов;
3. Как работает новый оператор
废话不多说,直接上代码
var newObj = function(func){
var t = {}
t.prototype = func.prototype
var o = t
var k =func.call(o);
if(typeof k === 'object'){
return k;
}else{
return o;
}
}
var parent1 = newObj(Parent)等价于new操作
1.一个新对象被创建,它继承自func.prototype。
2.构造函数func 被执行,执行的时候,相应的参数会被传入,同时上下文(this) 会被指定为这个新实例。
3.如果构造函数返回了一个新对象,那么这个对象会取代整个new出来的结果,如果构造函数没有返回对象,
那么new出来的结果为步骤1创建的对象。
наследовать
1. Наследование реализации конструктора (наследование конструкции)
function Parent(){
this.name = 'parent1'
this.play = [1,2,3]
}
function Child{
Parent.call(this);//apply
this.type = 'parent2';
}
Parent.prototype.id = '1'
var child1 = new Child()
console.log(child1.name)//parent1
console.log(child1.id)//undefined
//以下代码看完继承方式2,再回过头来看
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[1,2,3]
从上面构造继承的代码可以看出,构造继承实现了继承,
打印出来父级的name属性,但是实例对象并没有访问到父级原型上面到属性;
II. Цепочка наследования прототипа
function Parent(){
this.name = 'parent'
this.play = [1,2,3]
}
function Child(){
this.type = 'child';
}
Child.prototype = new Parent();
Parent.prototype.id = '1';
var child1 = new Child();
console.log(child1.name)//parent1
console.log(child1.id)//1
从这里可以看出,原型继承弥补了构造继承到缺点,继承了原型上到属性;
但是下面再做一个操作:
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[2,2,3]
这里我只是改变了实例对象child1到play数组,但是实例打印实例对象child2到paly数组,发现也跟着变化
了,所以可以得出结论,原型链继承引用类型到属性,在所有实例对象上面改变该属性,所有实例对象该属性都会
变化,这样肯定就存在问题,现在我们回到继承方式1(构造继承),会发现构造继承不会存在这个问题,所以
其实构造继承和原型链继承完全可以互补,由此我们引入第三种继承方式;
额外解释:这里通过一个原型链继承,我们再来回顾一下对原型链的理解,上面代码,我们进行了一个操作:
Child.prototype = new Parent();
这个操作把父类的实例赋值给子类的原型,然后结合上面原型链的关系图,我们再来理一下(为了阅读方便,复
制上图到此处):
Child.prototype = новый Родитель(); Эта операция передает экземпляр родительского класса прототипу Child, поэтому с помощью этого мы можем найти имя родителя, который является цепочкой прототипов, слой за слоем, как цепочка;
3. Комбинированное наследование
function Parent(){
this.name = 'parent1'
this.play = [1,2,3]
}
function Child{
Parent.call(this);//apply
this.type = 'parent2';
}
Child.prototype = new Parent();
Parent.prototype.id = '1'
var child1 = new Child()
console.log(child1.name)//parent1
console.log(child1.id)//1
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[1,2,3]
从上面代码可以看出,组合继承就是把构造继承和原型链继承组合在一起,把他们的优势互补,从而弥补了各自的
缺点;那么组合继承就完美了吗?我们继续思考,从代码中可以发现,我们调用了两次Parent函数,一次是
new Parent(),一次是Parent.call(this),是否可以优化呢?我们引入第四种继承方式;
4. Комбинированное наследование (оптимизация 1)
function Parent(){
this.name = 'parent1'
this.play = [1,2,3]
}
function Child{
Parent.call(this);//apply
this.type = 'parent2';
}
Child.prototype = Parent.prototype;//这里改变了
Parent.prototype.id = '1'
var child1 = new Child()
console.log(child1.name)//parent1
console.log(child1.id)//1
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[1,2,3]
我们改成Child.prototype = Parent.prototype,这样就只调用一次Parent了,解决了继承方式3的问题,
好吧,我们继续思考,这样就没有问题了吗,我们做如下操作:
console.log(Child.prototype.constructor)//Parent
这里我们打印发现Child的原型的构造器成了Parent,按照我们的理解应该是Child,这就造成了构造器紊乱,
所以我们引入第五种继承优化
5. Комбинаторное наследование (оптимизация 2)
function Parent(){
this.name = 'parent1'
this.play = [1,2,3]
}
function Child{
Parent.call(this);//apply
this.type = 'parent2';
}
Child.prototype = Parent.prototype;
Child.prototype.constructor = Child//这里改变了
Parent.prototype.id = '1'
var child1 = new Child()
console.log(child1.name)//parent1
console.log(child1.id)//1
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[1,2,3]
现在我们打印
console.log(Child.prototype.constructor)//Child
这里就解决了问题,但是我们继续打印
console.log(Parent.prototype.constructor)//Child
发现父类的构造器也出现了紊乱,所有我们通过一个中间值来解决这个问题,最终版本为:
function Parent(){
this.name = 'parent1'
this.play = [1,2,3]
}
function Child{
Parent.call(this);//apply
this.type = 'parent2';
}
var obj = {};
obj.prototype = Parent.prototype;
Child.prototype = obj;
//上面三行代码也可以简化成Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child
console.log(Child.prototype.constructor)//Child
console.log(Parent.prototype.constructor)//Parent
用一个中间obj,完美解决了这个问题