JS寄生式组合继承详解:工厂与构造模式的结合
本文深入解析JavaScript中寄生式组合继承的实现原理。通过结合工厂模式与构造函数模式,该方案解决了传统组合继承中父类构造函数被调用两次导致的效率问题,同时保持了原型链的完整性,是引用类型继承的最佳实践之一。
序言
在前文《JS精粹,原型链继承和构造函数继承的 “毛病”》中,我们探讨了原型链继承、构造函数继承以及组合继承这三种方式。
而在另一篇文章《蓦然回首,“工厂、构造、原型”设计模式,正在灯火阑珊处》里,我们介绍了用于创建对象的三种设计模式:工厂设计模式、构造设计模式和原型设计模式。
由此可见,在 JavaScript 中实现面向对象编程(特别是继承能力),离不开上述三种设计模式的支持。
原型链与构造函数的结合构成了组合继承。
本篇将介绍一种新的继承方式:寄生式组合继承。它是由工厂模式和构造函数模式组合而成的。
即:工厂模式加上构造函数模式,构成了寄生式组合继承。
正文
之所以提出新的继承方式,是因为原型链继承和构造函数继承存在明显的缺陷。
- 原型链继承的缺点在于:所有继承的属性和方法都会在对象实例间共享,无法实现实例属性的私有化。
- 构造函数继承的缺点在于:子类无法访问父类原型上的方法。
为了解决上述问题,组合继承应运而生。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
function SubType(name, age){
SuperType.call(this, name) // 构造函数继承 (两次调用父类构造函数)
this.age = age;
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
SubType.prototype = new SuperType() // 原型链继承 (一次调用父类构造函数)
SubType.prototype.sayAge = function() {
console.log(this.age);
}
let s1 = new SubType("Nicholas", 29)
let s2= new SubType("Greg", 27)
s1.colors.push("yellow")
console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']
console.log(s2.colors) // ['red', 'blue', 'green']
s1.sayName() // Nicholas
s2.sayName() // Greg
s1.sayAge() // 29
s2.sayAge() // 27
然而,这种方式存在效率问题:父类构造函数会被调用两次。一次是在子类构造函数内部通过 call 调用,另一次是在为子类原型赋值时(new SuperType())调用。
从本质上看,子类原型最终需要包含超类对象的所有实例属性,因此子类构造函数只需在执行时重写自己的原型即可。
基于此,我们引入一个新的思路。
不再通过调用父类构造函数来给子类原型赋值,而是获取父类原型的一个副本。利用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
其核心代码逻辑是:通过工厂模式的方式,增强一个新对象。
function createAnother(original){
let clone = Object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象
}
我们将组合继承的代码进行改造,完整实现代码如下。
function inheritPrototype(subType, superType) {
let prototype = Object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 赋值对象
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
function SubType(name, age) {
SuperType.call(this, name); // 构造函数继承(只调了一次)
this.age = age;
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
inheritPrototype(SubType, SuperType); // 寄生继承
SubType.prototype.sayAge = function() {
console.log(this.age);
};
let s1 = new SubType("Nicholas", 29)
let s2= new SubType("Greg", 27)
s1.colors.push("yellow")
console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']
console.log(s2.colors) // ['red', 'blue', 'green']
s1.sayName() // Nicholas
s2.sayName() // Greg
s1.sayAge() // 29
s2.sayAge() // 27
这种写法只调用了一次 SuperType 构造函数,从而避免了在 SubType.prototype 上产生不必要且用不到的属性;同时,原型链保持不变,instanceof 操作符和 isPrototypeOf() 方法依然正常有效。
寄生式组合继承可以被视为引用类型继承的最佳模式。
不过,这里的增强写法理解起来较为晦涩,为什么父类的显示原型构造函数等于子类?
SuperType.prototype.constructor=== SubType // true
这大概是为了通过寄生实现:父类和子类都由同一函数构造,从而保持原型链的一致性。
SubType === SubType.prototype.constructor // true
SuperType.prototype.constructor === SubType.prototype.constructor // true
结语
只要涉及 JavaScript 的继承实现,就必然离不开工厂、构造和原型这三种设计模式。
原型链加上构造函数,构成了组合继承。
工厂模式加上构造函数,构成了寄生式组合继承。
组合继承和寄生式组合继承是目前最常用的两种继承方式。
......
u1s1,class 出来前,写 JS 实现继承,是真滴麻烦QAQ
以上就是JS继承与工厂构造及原型设计模式详解的详细内容,更多关于JS继承与设计模式的资料请关注本站其它相关文章!
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
