深入理解原型链
1.原型链
原型链作为实现继承的主要方法:其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
首先我们得清楚构造函数(constructor),原型对象(prototype)和实例的三者关系。
每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。所以当我们让一个构造函数的原型对象等于另一个类型的实例
function A(){};
function B(){};
B.prototype=new A();
var instance=new B();
instance的原型对象是B.prototype,而B.prototype又等于构造函数A的一个实例对象,A的实例对象的原型对象是A.prototype,这样就形成了一条原型链了。
实例:
function SuperType(){//定义一个SuperType类型
this.property = true;//给实例对象添加一个属性
}
SuperType.prototype.getSuperValue = function(){//给原型对象添加一个方法
return this.property;
};
function SubType(){//定义一个SubType类型
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();//创建一个SuperType的实例对象,并把它赋值给SubType.prototype
SubType.prototype.getSubValue = function (){//给原型对象添加一个方法
return this.subproperty;//返回实例属性
};
var instance = new SubType();//创建一个对象实例
alert(instance.getSuperValue()); //true
创建一个SuperType的实例对象,并把它赋值给SubType.prototype,也就是说,原来存在于SuperType的实例对象中的所有属性和方法,现在也存在于SubType.prototype中了,我们在给SubType.prototype添加了一个方法,这样就在继承SuperType的属性和方法的基础上又添加了一个新方法。
我们没有使用SubType默认提供的原型,而是给它换了一个新原型;这个新原型就是SuperType的实例。于是,新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向了SuperType的原型。
现在instance指向SubType的原型,Subtype的原型指向SuperType的原型。getSuperValue()方法仍然在SuperType.prototype中,但property则位于SubType.prototype中了。因为property是一个实例属性,而getSuperValue()方法则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中了。
注意:
instance.constructor此时指向的是SuperType,这是因为原来的SubType.prototype被重写的缘故。任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"SubType.prototype = new SuperType();"这一行,SubType.prototype.constructor是指向SubType的;加了这一行以后,SubType.prototype.constructor指向SuperType。更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。因此,在运行"SubType.prototype = new SuperType();"这一行之后,instance.constructor也指向SuperType!这显然会导致继承链的紊乱(instance明明是用构造函数SubType生成的),如果我们想要完全符合继承,可以将将SuperType.prototype对象的constructor值改为SuperType。
2.谨慎地定义方法
通过原型实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承 SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},
someOtherMethod : function (){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
3.原型链的问题
原型链虽然非常强大,但是它也存在一些问题,其中最大的问题就是:包含引用类型的原型属性会被所有实例共享。
而这也正是为什么要在构造函数中,而不是在原型对象定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就变成了现在的原型属性了。
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"
SuperType定义了一个colors的实例对象属性,该属性包含一个数组(引用类型值),SuperType的每个对实例都会有自己的colors属性。当SubType通过原型链继承了SuperType之后,SubType.prototype就成了SuperType的一个实例,因此它也拥有了一个它自己的colors属性,就更专门创建了SubType.prototype.colors属性一样。也正因为如此,所以SubType构造函数的实例对象都会通过原型链继承这个属性,当其中某个实例对象改变了这个属性,在其他实例中也会体现出来。
在看看下面这种情况
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//inherit from SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors= ["blue", "green"];//重写了colors属性,现在这个属性是属于instance1自己的,也就是不再是对原型对象上的属性引用了
alert(instance1.colors); //"blue,green"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"//引用(继承)原型对象上的属性,实际属性还是部署在原型对象上的。
这个实例与前面的实例有什么区别:colors都是原型属性,所以每个实例对象都会继承这个属性,如果没有重写这个属性,那么实例对象的这个属性都是对原型对象的该属性的引用,改变它会反映到其他实例对象中,如果重写了,那么这个属性就会覆盖原来的属性,从而变成自身的属性。
4.确定原型和实例的关系的方法:
(1)通过instanceof
alert(instance instanceof Object) //true
(2)通过isPrototypeof
alert(Object.prototype.isPrototypeof(instance)); //true
参考:
(1)JavaScript高级程序设计第六章