1.繼承(接口繼承和實現(xiàn)繼承)
繼承是 OO 語言中的一個最為人津津樂道的概念。許多 OO 語言都支持兩種繼承方式:接口繼承和實現(xiàn)繼承。接口繼承只繼承方法簽名宝当,而實現(xiàn)繼承則繼承實際的方法。如前所述,由于函數(shù)沒有簽名包帚,在 ECMAScript 中無法實現(xiàn)接口繼承。 ECMAScript 只支持實現(xiàn)繼承运吓,而且其實現(xiàn)繼承主要是依靠原型鏈來實現(xiàn)的渴邦。
2.實現(xiàn)繼承的幾種方式:
方式一:(原型鏈)
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();
alert(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ù)和原型之間的關系如圖 6-4 所示。
在上面的代碼中界酒,我們沒有使用 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 被重寫了的緣故饶囚。
原型搜索機制:通過實現(xiàn)原型鏈帕翻,本質上擴展了本章前面介紹的原型搜索機制鸠补。讀者大概還記得,當以讀取模式訪問一個實例屬性時嘀掸,首先會在實例中搜索該屬性紫岩。如果沒有找到該屬性,則會繼續(xù)搜索實例的原型睬塌。在通過原型鏈實現(xiàn)繼承的情況下泉蝌,搜索過程就得以沿著原型鏈繼續(xù)向上。就拿上面的例子來說衫仑,調用instance.getSuperValue()會經(jīng)歷三個搜索步驟: 1)搜索實例梨与; 2)搜索 SubType.prototype堕花;3)搜索 SuperType.prototype文狱,最后一步才會找到該方法。在找不到屬性或方法的情況下缘挽,搜索過程總是要一環(huán)一環(huán)地前行到原型鏈末端才會停下來瞄崇。
1. 別忘記默認的原型
事實上,前面例子中展示的原型鏈還少一環(huán)壕曼。我們知道苏研,所有引用類型默認都繼承了 Object,而這個繼承也是通過原型鏈實現(xiàn)的腮郊。大家要記住摹蘑,所有函數(shù)的默認原型都是 Object 的實例,因此默認原型都會包含一個內部指針轧飞,指向 Object.prototype衅鹿。這也正是所有自定義類型都會繼承 toString()、valueOf()等默認方法的根本原因过咬。所以大渤,我們說上面例子展示的原型鏈中還應該包括另外一個繼承層次。圖 6-5 為我們展示了該例子中完整的原型鏈掸绞。
一句話泵三,SubType 繼承了 SuperType,而 SuperType 繼承了 Object衔掸。當調用 instance.toString()時烫幕,實際上調用的是保存在 Object.prototype 中的那個方法。
2. 確定原型和實例的關系
可以通過兩種方式來確定原型和實例之間的關系敞映。第一種方式是使用instanceof 操作符较曼,只要用這個操作符來測試實例與原型鏈中出現(xiàn)過的構造函數(shù),結果就會返回 true驱显。以下幾行代碼就說明了這一點诗芜。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
由于原型鏈的關系瞳抓,我們可以說 instance 是 Object、 SuperType 或 SubType 中任何一個類型的實例伏恐。因此孩哑,測試這三個構造函數(shù)的結果都返回了 true。
第二種方式是使用 isPrototypeOf()方法翠桦。同樣横蜒,只要是原型鏈中出現(xiàn)過的原型,都可以說是該原型鏈所派生的實例的原型销凑,因此 isPrototypeOf()方法也會返回 true丛晌,如下所示。
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
3. 謹慎地定義方法
子類型有時候需要重寫超類型中的某個方法斗幼,或者需要添加超類型中不存在的某個方法澎蛛。但不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之后蜕窿。來看下面的例子谋逻。
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;
};
//重寫超類型中的方法
SubType.prototype.getSuperValue = function (){
return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false
在以上代碼中,加粗的部分是兩個方法的定義桐经。第一個方法 getSubValue()被添加到了 SubType中毁兆。第二個方法 getSuperValue()是原型鏈中已經(jīng)存在的一個方法,但重寫這個方法將會屏蔽原來的那個方法阴挣。 換句話說气堕,當通過 SubType 的實例調用 getSuperValue()時,調用的就是這個重新定義的方法畔咧;但通過 SuperType 的實例調用 getSuperValue()時茎芭,還會繼續(xù)調用原來的那個方法。這里要格外注意的是盒卸,必須在用 SuperType 的實例替換原型之后骗爆,再定義這兩個方法。還有一點需要提醒讀者蔽介,即在通過原型鏈實現(xiàn)繼承時摘投,不能使用對象字面量創(chuàng)建原型方法。因為這
樣做就會重寫原型鏈虹蓄,如下面的例子所示犀呼。
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!
以上代碼展示了剛剛把 SuperType 的實例賦值給原型薇组,緊接著又將原型替換成一個對象字面量而導致的問題外臂。由于現(xiàn)在的原型包含的是一個 Object 的實例,而非 SuperType 的實例律胀,因此我們設想中的原型鏈已經(jīng)被切斷——SubType 和 SuperType 之間已經(jīng)沒有關系了宋光。
4. 原型鏈的問題
原型鏈雖然很強大貌矿,可以用它來實現(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"
問題:
1.這個例子中的 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)充分證實了這一點漱办。
2.原型鏈的第二個問題是:在創(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ù)余蟹。通過使用 call()方法(或 apply()方法也可以)麦向,我們實際上是在(未來將要)新創(chuàng)建的 SubType 實例的環(huán)境下調用了 SuperType 構造函數(shù)。這樣一來客叉,就會在新 SubType 對象上執(zhí)行 SuperType()函數(shù)中定義的所有對象初始化代碼诵竭。結果,SubType 的每個實例就都會具有自己的 colors 屬性的副本了兼搏。
1. 傳遞參數(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ù)后纺裁,再添加應該在子類型中定義的屬性诫肠。
2. 借用構造函數(shù)的問題
如果僅僅是借用構造函數(shù),那么也將無法避免構造函數(shù)模式存在的問題——方法都在構造函數(shù)中定義欺缘,因此函數(shù)復用就無從談起了栋豫。而且,在超類型的原型中定義的方法谚殊,對子類型而言也是不可見的丧鸯,結果所有類型都只能使用構造函數(shù)模式。考慮到這些問題嫩絮,借用構造函數(shù)的技術也是很少單獨使用的丛肢。
方式三:(組合繼承)
組合繼承(combination inheritance),有時候也叫做偽經(jīng)典繼承絮记,指的是將原型鏈和借用構造函數(shù)的技術組合到一塊摔踱,從而發(fā)揮二者之長的一種繼承模式。其背后的思路是使用原型鏈實現(xiàn)對原型屬性和方法的繼承怨愤,而通過借用構造函數(shù)來實現(xiàn)對實例屬性的繼承派敷。這樣,既通過在原型上定義方法實現(xiàn)了函數(shù)復用,又能夠保證每個實例都有它自己的屬性篮愉。下面來看一個例子腐芍。
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.constructor = SubType;
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
解釋:
在這個例子中, SuperType 構造函數(shù)定義了兩個屬性: name 和 colors试躏。 SuperType 的原型定義了一個方法 sayName()猪勇。 SubType 構造函數(shù)在調用 SuperType 構造函數(shù)時傳入了 name 參數(shù),緊接著又定義了它自己的屬性 age颠蕴。然后泣刹,將 SuperType 的實例賦值給 SubType 的原型,然后又在該新原型上定義了方法 sayAge()犀被。這樣一來椅您,就可以讓兩個不同的 SubType 實例既分別擁有自己屬性——包括 colors 屬性,又可以使用相同的方法了寡键。
優(yōu)點:
組合繼承避免了原型鏈和借用構造函數(shù)的缺陷掀泳,融合了它們的優(yōu)點,成為 JavaScript 中最常用的繼承模式西轩。而且员舵, instanceof 和 isPrototypeOf()也能夠用于識別基于組合繼承創(chuàng)建的對象。
方式四:(原型式繼承)
道格拉斯·克羅克福德在 2006 年寫了一篇文章藕畔,題為 Prototypal Inheritance in JavaScript (JavaScript中的原型式繼承)马僻。在這篇文章中,他介紹了一種實現(xiàn)繼承的方法劫流,這種方法并沒有使用嚴格意義上的構造函數(shù)巫玻。他的想法是借助原型可以基于已有的對象創(chuàng)建新對象,同時還不必因此創(chuàng)建自定義類型祠汇。為了達到這個目的,他給出了如下函數(shù)熄诡。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在 object()函數(shù)內部可很,先創(chuàng)建了一個臨時性的構造函數(shù),然后將傳入的對象作為這個構造函數(shù)的原型凰浮,最后返回了這個臨時類型的一個新實例钥平。從本質上講师枣, object()對傳入其中的對象執(zhí)行了一次淺復制。來看下面的例子。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
克羅克福德主張的這種原型式繼承是牢,要求你必須有一個對象可以作為另一個對象的基礎。如果有這么一個對象的話铆铆,可以把它傳遞給 object()函數(shù)建车,然后再根據(jù)具體需求對得到的對象加以修改即可。在這個例子中,可以作為另一個對象基礎的是 person 對象贱鄙,于是我們把它傳入到 object()函數(shù)中劝贸,然后該函數(shù)就會返回一個新對象。這個新對象將 person 作為原型逗宁,所以它的原型中就包含一個基本類型值屬性
和一個引用類型值屬性映九。這意味著 person.friends 不僅屬于 person 所有,而且也會被 anotherPerson以及 yetAnotherPerson 共享瞎颗。實際上件甥,這就相當于又創(chuàng)建了 person 對象的兩個副本。
ECMAScript 5 通過新增 Object.create()方法規(guī)范化了原型式繼承哼拔。這個方法接收兩個參數(shù):一個用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象嚼蚀。在傳入一個參數(shù)的情況下,Object.create()與 object()方法的行為相同管挟。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create()方法的第二個參數(shù)與Object.defineProperties()方法的第二個參數(shù)格式相同:每個屬性都是通過自己的描述符定義的轿曙。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性。例如:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
{
name: {
value: "Greg"
}
});
alert(anotherPerson.name); //"Greg"
支持版本:支持 Object.create()方法的瀏覽器有 IE9+僻孝、 Firefox 4+导帝、 Safari 5+、 Opera 12+和 Chrome穿铆。在沒有必要興師動眾地創(chuàng)建構造函數(shù)您单,而只想讓一個對象與另一個對象保持類似的情況下,原型式繼承是完全可以勝任的荞雏。缺點:不過別忘了虐秦,包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式一樣凤优。
方式五:(寄生式繼承)
寄生式(parasitic)繼承是與原型式繼承緊密相關的一種思路悦陋,并且同樣也是由克羅克福德推而廣之的。寄生式繼承的思路與寄生構造函數(shù)和工廠模式類似筑辨,即創(chuàng)建一個僅用于封裝繼承過程的函數(shù)俺驶,該函數(shù)在內部以某種方式來增強對象,最后再像真地是它做了所有工作一樣返回對象棍辕。以下代碼示范了寄生式繼承模式暮现。
function createAnother(original){
var clone = object(original); //通過調用函數(shù)創(chuàng)建一個新對象
clone.sayHi = function(){ //以某種方式來增強這個對象
alert("hi");
};
return clone; //返回這個對象
}
在這個例子中, createAnother()函數(shù)接收了一個參數(shù)楚昭,也就是將要作為新對象基礎的對象栖袋。然后,把這個對象(original)傳遞給 object()函數(shù)抚太,將返回的結果賦值給 clone塘幅。再為 clone 對象添加一個新方法 sayHi()昔案,最后返回 clone 對象∩慰椋可以像下面這樣來使用 createAnother()函數(shù):
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
這個例子中的代碼基于 person 返回了一個新對象——anotherPerson爱沟。新對象不僅具有 person的所有屬性和方法,而且還有自己的 sayHi()方法匆背。
使用范圍:在主要考慮對象而不是自定義類型和構造函數(shù)的情況下呼伸,寄生式繼承也是一種有用的模式。前面示范繼承模式時使用的 object()函數(shù)不是必需的钝尸;任何能夠返回新對象的函數(shù)都適用于此模式括享。
注意:使用寄生式繼承來為對象添加函數(shù),會由于不能做到函數(shù)復用而降低效率珍促;這一點與構造函數(shù)模式類似铃辖。
方式六:(寄生組合式繼承)
前面說過,組合繼承是 JavaScript 最常用的繼承模式猪叙;不過娇斩,它也有自己的不足。組合繼承最大的問題就是無論什么情況下穴翩,都會調用兩次超類型構造函數(shù):一次是在創(chuàng)建子類型原型的時候犬第,另一次是在子類型構造函數(shù)內部。沒錯芒帕,子類型最終會包含超類型對象的全部實例屬性歉嗓,但我們不得不在調用子類型構造函數(shù)時重寫這些屬性。再來看一看下面組合繼承的例子背蟆。
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); //第二次調用 SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次調用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
加粗字體的行中是調用 SuperType 構造函數(shù)的代碼鉴分。在第一次調用 SuperType 構造函數(shù)時,SubType.prototype 會得到兩個屬性: name 和 colors带膀;它們都是 SuperType 的實例屬性志珍,只不過現(xiàn)在位于 SubType 的原型中。當調用 SubType 構造函數(shù)時本砰,又會調用一次 SuperType 構造函數(shù)碴裙,這一次又在新對象上創(chuàng)建了實例屬性 name 和 colors。于是点额,這兩個屬性就屏蔽了原型中的兩個同名屬性。圖 6-6 展示了上述過程莺琳。
如圖 6-6 所示还棱,有兩組 name 和 colors 屬性:一組在實例上,一組在 SubType 原型中惭等。這就是調用兩次 SuperType 構造函數(shù)的結果珍手。好在我們已經(jīng)找到了解決這個問題方法——寄生組合式繼承。
所謂寄生組合式繼承,即通過借用構造函數(shù)來繼承屬性琳要,通過原型鏈的混成形式來繼承方法寡具。其背后的基本思路是:不必為了指定子類型的原型而調用超類型的構造函數(shù),我們所需要的無非就是超類型原型的一個副本而已稚补。本質上童叠,就是使用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型课幕。寄生組合式繼承的基本模式如下所示厦坛。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創(chuàng)建對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
這個示例中的 inheritPrototype()函數(shù)實現(xiàn)了寄生組合式繼承的最簡單形式。這個函數(shù)接收兩個參數(shù):子類型構造函數(shù)和超類型構造函數(shù)乍惊。在函數(shù)內部杜秸,第一步是創(chuàng)建超類型原型的一個副本。第二步是為創(chuàng)建的副本添加 constructor 屬性润绎,從而彌補因重寫原型而失去的默認的 constructor 屬性撬碟。最后一步,將新創(chuàng)建的對象(即副本)賦值給子類型的原型莉撇。這樣呢蛤,我們就可以用調用 inheritPrototype()函數(shù)的語句,
去替換前面例子中為子類型原型賦值的語句了稼钩,例如:
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創(chuàng)建對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
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;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
這個例子的高效率體現(xiàn)在它只調用了一次 SuperType 構造函數(shù)顾稀,并且因此避免了在 SubType.prototype 上面創(chuàng)建不必要的、多余的屬性坝撑。與此同時静秆,原型鏈還能保持不變;因此巡李,還能夠正常使用instanceof 和 isPrototypeOf()抚笔。開發(fā)人員普遍認為寄生組合式繼承是引用類型最理想的繼承范式。
首次應該次繼承js庫: YUI 的 YAHOO.lang.extend()方法采用了寄生組合繼承侨拦,從而讓這種模式首次出現(xiàn)在了一個應用非常廣泛的 JavaScript 庫中殊橙。要了解有關 YUI 的更多信息,請訪問http://developer. yahoo.com/yui/狱从。
總結:
ECMAScript 支持面向對象(OO)編程膨蛮,但不使用類或者接口。對象可以在代碼執(zhí)行過程中創(chuàng)建和增強季研,因此具有動態(tài)性而非嚴格定義的實體敞葛。在沒有類的情況下,可以采用下列模式創(chuàng)建對象与涡。
? 工廠模式惹谐,使用簡單的函數(shù)創(chuàng)建對象持偏,為對象添加屬性和方法,然后返回對象氨肌。這個模式后來被構造函數(shù)模式所取代鸿秆。
? 構造函數(shù)模式,可以創(chuàng)建自定義引用類型怎囚,可以像創(chuàng)建內置對象實例一樣使用 new 操作符卿叽。不過,構造函數(shù)模式也有缺點桩了,即它的每個成員都無法得到復用附帽,包括函數(shù)。由于函數(shù)可以不局限于任何對象(即與對象具有松散耦合的特點)井誉,因此沒有理由不在多個對象間共享函數(shù)蕉扮。
? 原型模式,使用構造函數(shù)的 prototype 屬性來指定那些應該共享的屬性和方法颗圣。組合使用構造函數(shù)模式和原型模式時喳钟,使用構造函數(shù)定義實例屬性,而使用原型定義共享的屬性和方法在岂。
JavaScript 主要通過原型鏈實現(xiàn)繼承奔则。原型鏈的構建是通過將一個類型的實例賦值給另一個構造函數(shù)的原型實現(xiàn)的。這樣蔽午,子類型就能夠訪問超類型的所有屬性和方法易茬,這一點與基于類的繼承很相似。原型鏈的問題是對象實例共享所有繼承的屬性和方法及老,因此不適宜單獨使用抽莱。解決這個問題的技術是借用構造函數(shù),即在子類型構造函數(shù)的內部調用超類型構造函數(shù)骄恶。這樣就可以做到每個實例都具有自己的屬性食铐,同時還能保證只使用構造函數(shù)模式來定義類型。使用最多的繼承模式是組合繼承僧鲁,這種模式使用原型鏈繼承共享的屬性和方法虐呻,而通過借用構造函數(shù)繼承實例屬性。
此外寞秃,還存在下列可供選擇的繼承模式斟叼。
1.? 原型式繼承,可以在不必預先定義構造函數(shù)的情況下實現(xiàn)繼承春寿,其本質是執(zhí)行對給定對象的淺復制犁柜。而復制得到的副本還可以得到進一步改造。
2.? 寄生式繼承堂淡,與原型式繼承非常相似馋缅,也是基于某個對象或某些信息創(chuàng)建一個對象,然后增強對象绢淀,最后返回對象萤悴。為了解決組合繼承模式由于多次調用超類型構造函數(shù)而導致的低效率問題,可以將這個模式與組合繼承一起使用皆的。
3.? 寄生組合式繼承覆履,集寄生式繼承和組合繼承的優(yōu)點與一身,是實現(xiàn)基于類型繼承的最有效方式费薄。