原型链的基本概念以及用法
Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” property. Furthermore, a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain. When a reference is made to a property in an object, that reference is to the property of that name in the first object in the prototype chain that contains a property of that name. In other words, first the object mentioned directly is examined for such a property; if that object contains the named property, that is the property to which the reference refers; if that object does not contain the named property, the prototype for that object is examined next; and so on. – ECMAScript2020
ECMAScript中描述了原型链的概念,将原型链作为实现继承的主要方法**。基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。**
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
分析红宝书上的原型链示例代码, 可以看到SubType.prototype现在是SuperType的实例,因此拥有作为SuperType实例的全部属性和方法。
需要注意的是instance.consturctor现在指向 SuperType
,这是因为SubType的原型指向了另一个对象—— SuperType.prototype
,而 SuperType.prototype
中的 constructor
指向 SuperType
我们以调用 instance.getSuperValue() 为例来说明下原型链的搜索流程:
- 搜索instance实例
- 搜索SubType.prototype
- 搜索SuperType.prototype, 找到getSuperValue()方法
原型链的问题
原型链中比较明显问题为:包含引用类型值的原型属性会被所有实例共享,也就是说一个实例对属性进行改变,会导致所有的属性跟着一起变,来看下面的例子简单理解下:
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//inherit from SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
借用构造函数
可以看到 instance1 对 colors 进行修改后, instance2 中的 colors 也跟着一起改变,因为此时 colors 是共享的,解决方法可以使用借用构造函数(constructor stealing):即在子类构造函数的内部调用超类型的构造函数。
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//inherit from SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
组合继承
借用构造函数虽然解决了共用属性的问题,但是不能做到方法共享,因此推荐使用组合继承(combination inheritance),组合使用原型链和借用构造函数
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name); // 第二次调用超类型构造函数
this.age = age;
}
SubType.prototype = new SuperType(); // 第一次调用超类型构造函数
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
寄生组合式继承
组合继承已经可以很好得完成继承功能,但我们不难发现还有改进的地方:组合继承无论在什么地方,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。第一次的调用其实是无意义的,因为第一次在子类型原型中初始化的夫类原型属性都会被第二次在子类型构造函数内部调用的重写,这方面我们可以做一下优化。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // create object
prototype.constructor = subType; // augment object
subType.prototype = prototype; // assign object
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
可以看到通过上述代码进行创建对象,只用调用一次超类型构造函数,优化了组合继承的缺点,是目前我所知道的最优解,欢迎读者留言一起交流。
参考
Objects
Javascript高级程序设计(第三版)