前面介绍的组合继承最大的问题就是无论什么情况下,都会调用两次父类型的构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性:
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。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。