比如,現在有一個”動物”物件的建構函式。
function Animal(name){
this.species = "動物";
this.animals = [name]
}
複製程式碼
還有一個”貓”物件的建構函式。
function Cat(name,color){
this.name = name;
this.color = color;
}
複製程式碼
原型鏈繼承
簡單回顧一下建構函式、原型和例項的關係:
每個建構函式都有一個原型物件,原型物件都包含一個指向建構函式的指標,而例項都包含一個指向原型物件的內部指標。
實現原型鏈有一種基本模式:
Cat.prototype = new Animal();
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
複製程式碼
要注意Cat.prototype.constructor現在指向的是Animal。這是因為原來Cat.prototype中的constructor被重寫了的緣故。
實際上,不是Cat的原型的constructor屬性被重寫了,而是Cat的原型指向了另一個物件——Animal的原型,而這個原型物件的constructor屬性指向的是Animal。
這顯然會導致繼承鏈的紊亂(cat1明明是用建構函式Cat生成的),因此我們必須手動糾正,將 Cat.prototype
物件的 constructor
值改為Cat。
Cat.prototype.constructor = Cat;
複製程式碼
原型鏈繼承最主要的問題來自包含引用型別值的原型。而且原型鏈在建立子型別的例項時不能向父型別的建構函式中傳遞引數。實踐中很少會單獨使用原型鏈。
借用建構函式繼承
在解決原型中包含引用型別值所帶來問題的過程中,使用了借用建構函式(constructor stealing)技術,有時候也叫做 偽造物件
或 經典繼承
。使用 call
或 apply
方法,在子型別建構函式的內部呼叫父型別建構函式:
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
複製程式碼
相對於原型鏈而言,借用建構函式有一個很大的優勢,即可以在子型別建構函式中向父型別建構函式傳遞引數。如果僅僅是借用建構函式,是不能實現函式複用, 只能繼承父類的例項屬性和方法。 考慮到這些問題,借用建構函式的技術也是很少單獨使用的。
組合繼承
組合繼承(combination inheritance),有時候也叫做偽經典繼承,指的是將原型鏈和借用建構函式的技術組合到一塊,從而發揮二者之長的一種繼承模式。 其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用建構函式來實現對例項屬性的繼承。 這樣,既通過在原型上定義方法實現了函式複用,又能夠保證每個例項都有它自己的屬性。
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
複製程式碼
組合繼承避免了原型鏈和借用建構函式的缺陷,融合了它們的優點,成為JavaScript中最常用的繼承模式。
寄生組合繼承
前面說過,組合繼承是JavaScript最常用的繼承模式;不過,它也有自己的不足。 組合繼承最大的問題就是無論什麼情況下,都會呼叫兩次父型別建構函式: 一次是在建立子型別原型的時候,另一次是在子型別建構函式內部。
所謂寄生組合式繼承,即通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法。
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
function inheritPrototype(Cat, Animal){
var prototype = Object.create(Animal.prototype);
prototype.constructor = Cat;
Cat.prototype = prototype;
}
inheritPrototype(Cat, Animal)
複製程式碼
寄生組合繼承 的高效率體現在它只呼叫了一次Animal建構函式。
寄生組合式繼承
集組合繼承的優點與一身,是實現基於型別繼承的最有效方式。