前面介紹的組合繼承最大的問題就是無論什么情況下,都會調用兩次父類型的構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。沒錯,子類型最終會包含父類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性:
function SuperColors(colorName) {
if (typeof colorName != "undefined") {
this.colors = ["red", "green", "blue", colorName];
} else {
this.colors = ["red", "green", "blue"];
}
}
SuperColors.prototype.sayColors = function () {
console.log(this.colors);
};
function SubColors(colorName, level) {
// 繼承屬性
SuperColors.call(this, colorName); // 第2次調用 SuperColors
this.level = level;
}
SubColors.prototype = new SuperColors(); // 第1ss次調用 SuperColors
SubColors.prototype.sayLevel = function () {
console.log(this.level);
};
解決這個問題的辦法是使用寄生組合式繼承。所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
其思路是:不必為了指定子類型的原型而調用父類型的構造函數,我們所需要的無非就是父類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承父類型的原型,然后再將結果指定給子類型的原型。
寄生組合式繼承的基本模式如下:
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype); // 創建對象
prototype.constructor = subType; // 增強對象
subType.prototype = prototype; // 指定對象
}
這個實例中 inheritPrototype 函數實現了寄生組合式繼承的最簡單形式:
- 創建父類型原型的一個副本;
- 為創建的副本添加 constructor 屬性,從而彌補因重寫原型而失去的默認的 constructor 屬性;
- 將新創建的對象(即副本)賦值給子類型的原型。
這樣,我們就可以調用 inheritPrototype 函數來修改前面的組合繼承的例子了:
function SuperColors(colorName) {
if (typeof colorName != "undefined") {
this.colors = ["red", "green", "blue", colorName];
} else {
this.colors = ["red", "green", "blue"];
}
}
SuperColors.prototype.sayColors = function () {
console.log(this.colors);
};
function SubColors(colorName, level) {
// 繼承屬性
SuperColors.call(this, colorName);
this.level = level;
}
inheritPrototype(SubColors, SuperColors);
SubColors.prototype.sayLevel = function () {
console.log(this.level);
};
var instance1 = new SubColors("gray", 0);
instance1.colors.push("white");
instance1.sayColors();
instance1.sayLevel();
var instance2 = new SubColors("black", 1);
instance2.sayColors();
instance2.sayLevel();
輸出結果:
輸出結果
這個例子的高效率體現在它只調用了一次 SuperColors 構造函數,并且因此避免了在 SubColors.prototype 上面創建不必要的、多余的屬性。與此同時,原型鏈還能保持不變;因此,還能夠正常使用 instanceof 和 isPrototypeOf。開發人員普遍認為寄生組合式繼承是引用類型最理想的繼承范式。