前面說過,組合繼承是JavaScript 最常用的繼承模式;不過,它也有自己的不足。
組合繼承最大的問題就是無論什麼情況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另一次是在子類型構造函數内部。
沒錯,子類型最終會包含超類型對象的全部執行個體屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。
再來看一看下面組合繼承的例子。
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 構造函數時,SubType.prototype 會得到兩個屬性:name 和colors;
它們都是SuperType 的執行個體屬性,隻不過現在位于SubType 的原型中。
當調用SubType 構造函數時,又會調用一次SuperType 構造函數,這一次又在新對象上建立了執行個體屬性name 和colors。
于是,這兩個屬性就屏蔽了原型中的兩個同名屬性。下圖 展示了上述過程。
如圖所示,有兩組name 和colors 屬性:一組在執行個體上,一組在SubType 原型中。
這就是調用兩次SuperType 構造函數的結果。
好在我們已經找到了解決這個問題方法——寄生組合式繼承。
所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鍊的混成形式來繼承方法。
其背後的基本思路是:不必為了指定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。
本質上,就是使用寄生式繼承來繼承超類型的原型,然後再将結果指定給子類型的原型。
寄生組合式繼承的基本模式如下所示。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //建立對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
這個示例中的inheritPrototype()函數實作了寄生組合式繼承的最簡單形式。
這個函數接收兩個參數:子類型構造函數和超類型構造函數。
在函數内部,第一步是建立超類型原型的一個副本。第二步是為建立的副本添加constructor 屬性,進而彌補因重寫原型而失去的預設的constructor 屬性。
最後一步,将新建立的對象(即副本)指派給子類型的原型。
這樣,我們就可以用調用inheritPrototype()函數的語句,去替換前面例子中為子類型原型指派的語句了,例如:
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);
};
這個例子的高效率展現在它隻調用了一次SuperType 構造函數,并且是以避免了在SubType.prototype 上面建立不必要的、多餘的屬性。
與此同時,原型鍊還能保持不變;是以,還能夠正常使用instanceof 和isPrototypeOf()。
開發人員普遍認為寄生組合式繼承是引用類型最理想的繼承範式。