原型鏈
ECMAScript中描述了原型鏈的概念民效,并將原型鏈作為實現(xiàn)繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法误褪。簡單回顧一下構造函數(shù)亭螟、原型和實例的關系:每個構造函數(shù)都有一個原型對象,原型對象都包含一個指向構造函數(shù)的指針榆俺,而實例都包含一個指向原型對象的內部指針售躁。那么,假如我們讓原型對象等于另一個類型的實例茴晋,結果會怎么樣呢陪捷?顯然,此時的原型對象將包含一個指向另一個原型的指針晃跺,相應地揩局,另一個原型中也包含著一個指向另一個構造函數(shù)的指針。假如另一個原型又是另一個類型的實例掀虎,那么上述關系依然成立凌盯,如此層層遞進,就構成了實例與原型的鏈條烹玉。這就是所謂原型鏈的基本概念驰怎。
實現(xiàn)原型鏈的基本模式
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;
};
var instance = new SubType();
console.log(instance.getSuperValue()); //true
以上代碼定義了兩個類型:SuperType和SubType。每個類型分別有一個屬性和一個方法二打。它們的主要區(qū)別是SubType繼承了SuperType县忌,而繼承是通過創(chuàng)建SuperType的實例,并將該實例賦給SubType.prototype實現(xiàn)的继效。實現(xiàn)的本質是重寫原型對象症杏,代之以一個新類型的實例。換句話說瑞信,原來存在于SuperType的實例中的所有屬性和方法厉颤,現(xiàn)在也存在于SubType.prototype中了。在確立了繼承關系之后凡简,我們給SubType.prototype添加了一個方法逼友,這樣就在繼承了SuperType的屬性和方法的基礎上又添加了一個新方法精肃。這個例子中的實例以及構造函數(shù)和原型之間的關系如圖
在上面的代碼中,我們沒有使用SubType默認提供的原型帜乞,而是給它換了一個新原型司抱;這個新原型就是SuperType的實例。于是黎烈,新原型不僅具有作為一個SuperType的實例所擁有的全部屬性和方法习柠,而且其內部還有一個指針,指向了SuperType的原型照棋。最終結果就是這樣的:instance指向SubType的原型津畸,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然還在SuperType.prototype中必怜,但property則位于SubType.prototype中。這是因為property是一個實例屬性后频,而getSuperValue()則是一個原型方法梳庆。既然SubType.prototype現(xiàn)在是SuperType的實例,那么property當然就位于該實例中了卑惜。此外膏执,要注意instance.constructor現(xiàn)在指向的是SuperType,這是因為原來SubType.prototype中的constructor被重寫了的緣故(下注)露久。
注:實際上更米,不是SubType的原型的constructor屬性被重寫了,而是SubType的原型指向了另一個對象——SuperType的原型毫痕,而這個原型對象的constructor屬性指向的是SuperType征峦。
確定原型和實例的關系
可以通過兩種方式來確定原型和實例之間的關系。第一種方式是使用instanceof操作符:
console.log(instance instanceof SuperType) //true
console.log(instance instanceof SubType) //true
由于原型鏈的關系消请,我們可以說instance是Object(所有函數(shù)的默認原型)栏笆、SuperType或SubType中任何一個類型的實例。因此臊泰,測試這三個構造函數(shù)的結果都返回了true蛉加。
第二種方式是使用isPrototypeOf()方法铁孵。同樣魔市,只要是原型鏈中出現(xiàn)過的原型,都可以說是該原型鏈所派生的實例的原型浪秘,因此isPrototypeOf()方法也會返回true需频,如下所示丁眼。
console.log(SuperType.prototype.isPrototypeOf(instance))
console.log(SubType.prototype.isPrototypeOf(instance))
謹慎地定義方法
function A(){
}
A.prototype.name = 'hello'
A.prototype.sayhi = function () {
return this.name + 'A'
}
function B(){
}
B.prototype = new A()
B.prototype.name = 'hi'
B.prototype.sayhi = function() {
return this.name + 'B'
}
var c = new B();
console.log(c.name); // hi
console.log(c.sayhi()); //hiB
以上代碼子類會屏蔽父類的同名的屬性和方法,還有一點需要注意贺辰,即在通過原型鏈實現(xiàn)繼承時户盯,不能使用對象字面量創(chuàng)建原型方法嵌施,因為這樣做就會重寫原型鏈。
原型鏈的問題
原型鏈雖然很強大莽鸭,可以用它來實現(xiàn)繼承吗伤,但它也存在一些問題。其中硫眨,最主要的問題來自包含引用類型值的原型足淆。引用類型值的原型屬性會被所有實例共享;而這也正是為什么要在構造函數(shù)中礁阁,而不是在原型對象中定義屬性的原因巧号。在通過原型來實現(xiàn)繼承時,原型實際上會變成另一個類型的實例姥闭。于是丹鸿,原先的實例屬性也就順理成章地變成了現(xiàn)在的原型屬性了。
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//繼承了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構造函數(shù)定義了一個colors屬性棚品,該屬性包含一個數(shù)組(引用類型值)靠欢。SuperType的每個實例都會有各自包含自己數(shù)組的colors屬性。當SubType通過原型鏈繼承了SuperType之后铜跑,SubType.prototype就變成了SuperType的一個實例门怪,因此它也擁有了一個它自己的colors屬性——就跟專門創(chuàng)建了一個SubType.prototype.colors屬性一樣。但結果是什么呢锅纺?結果是SubType的所有實例都會共享這一個colors屬性掷空。而我們對instance1.colors的修改能夠通過instance2.colors反映出來,就已經(jīng)充分證實了這一點囤锉。
原型鏈的第二個問題是:在創(chuàng)建子類型的實例時坦弟,不能向超類型的構造函數(shù)中傳遞參數(shù)。實際上官地,應該說是沒有辦法在不影響所有對象實例的情況下减拭,給超類型的構造函數(shù)傳遞參數(shù)。有鑒于此区丑,再加上前面剛剛討論過的由于原型中包含引用類型值所帶來的問題拧粪,實踐中很少會單獨使用原型鏈。
借用構造函數(shù)
在解決原型中包含引用類型值所帶來問題的過程中沧侥,開發(fā)人員開始使用一種叫做借用構造函數(shù)(constructor stealing)的技術(有時候也叫做偽造對象或經(jīng)典繼承)可霎。這種技術的基本思想相當簡單,即在子類型構造函數(shù)的內部調用超類型構造函數(shù)宴杀。別忘了癣朗,函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對象,因此通過使用apply()和call()方法也可以在(將來)新創(chuàng)建的對象上執(zhí)行構造函數(shù)
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//繼承了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"
傳遞參數(shù)
相對于原型鏈而言旺罢,借用構造函數(shù)有一個很大的優(yōu)勢旷余,即可以在子類型構造函數(shù)中向超類型構造函數(shù)傳遞參數(shù)绢记。
function SuperType(name){
this.name = name;
}
function SubType(){
//繼承了SuperType,同時還傳遞了參數(shù)
SuperType.call(this, "Nicholas");
//實例屬性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
以上代碼中的SuperType只接受一個參數(shù)name正卧,該參數(shù)會直接賦給一個屬性蠢熄。在SubType構造函數(shù)內部調用SuperType構造函數(shù)時,實際上是為SubType的實例設置了name屬性炉旷。為了確保SuperType構造函數(shù)不會重寫子類型的屬性签孔,可以在調用超類型構造函數(shù)后,再添加應該在子類型中定義的屬性窘行。
JavaScript高級程序設計(第3版) 讀書筆記
GitHub:JavaScript-Demo