https://www.muyiy.cn/blog/5/5.2.html#%E5%8E%9F%E5%9E%8B%E9%93%BE%E7%BB%A7%E6%89%BF
原型链继承
原型链继承的本质是重写原型对象,代之以一个新类型的实例。如下代码,新原型 Cat 不仅有 new Animal() 实例上的全部属性和方法,并且由于指向了 Animal 原型,所以还继承了Animal 原型上的属性和方法。
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
// 这里是关键,创建 Animal 的实例,并将该实例赋值给 Cat.prototype
// 相当于 Cat.prototype.__proto__ = Animal.prototype
Cat.prototype = new Animal();
var instance = new Cat();
instance.value = 'cat'; // 创建 instance 的自身属性 value
console.log(instance.run()); // cat is runing
问题 1
原型链继承方案中,原型实际上会变成另一个类型的实例,如下代码,Cat.prototype 变成了 Animal 的一个实例,所以 Animal 的实例属性 names 就变成了 Cat.prototype 的属性。
而原型属性上的引用类型值会被所有实例共享,所以多个实例对引用类型的操作会被篡改。如下代码,改变了 instance1.names 后影响了 instance2。
function Animal(){
this.names = [“cat”, “dog”];
}
function Cat(){}
Cat.prototype = new Animal();
var instance1 = new Cat();
instance1.names.push(“tiger”);
console.log(instance1.names); // [“cat”, “dog”, “tiger”]
var instance2 = new Cat();
console.log(instance2.names); // [“cat”, “dog”, “tiger”]
问题 2
子类型原型上的 constructor 属性被重写了,执行 Cat.prototype = new Animal() 后原型被覆盖,Cat.prototype 上丢失了 constructor 属性, Cat.prototype 指向了 Animal.prototype,而 Animal.prototype.constructor 指向了 Animal,所以 Cat.prototype.constructor 指向了 Animal。
Cat.prototype = new Animal();
Cat.prototype.constructor === Animal
// true
解决办法就是重写 Cat.prototype.constructor 属性,指向自己的构造函数 Cat。
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
Cat.prototype = new Animal();
// 新增,重写 Cat.prototype 的 constructor 属性,指向自己的构造函数 Cat
Cat.prototype.constructor = Cat;
问题 3
给子类型原型添加属性和方法必须在替换原型之后,原因在第二点已经解释过了,因为子类型的原型会被覆盖。
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 新增
Cat.prototype.getValue = function() {
return this.value;
}
var instance = new Cat();
instance.value = 'cat';
console.log(instance.getValue()); // cat
问题 4 属性遮蔽
改造上面的代码,在 Cat.prototype 上添加 run 方法,但是 Animal.prototype 上也有一个 run 方法,不过它不会被访问到,这种情况称为属性遮蔽 (property shadowing)。
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 新增
Cat.prototype.run = function() {
return 'cat cat cat';
}
var instance = new Cat();
instance.value = 'cat';
console.log(instance.run()); // cat cat cat
那如何访问被遮蔽的属性呢?通过 proto 调用原型链上的属性即可。
// 接上
console.log(instance.__proto__.__proto__.run()); // undefined is runing
小结
1.每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为**原型链 **
2.当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。
3.原型链的构建依赖于 proto,一层一层最终链接到 null。
4.instanceof 原理就是一层一层查找 proto,如果和 constructor.prototype 相等则返回 true,如果一直没有查找成功则返回 false。
5.原型链继承的本质是重写原型对象,代之以一个新类型的实例