深入理解原型鏈
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高級程序設計第六章