组合继承是原型链继承与借用构造函数继承的结合,既解决引用类型属性共享问题,又实现方法复用;需先调用父类构造函数初始化实例属性,再将父类实例赋值给子类原型并修复constructor。

JavaScript中组合继承是原型链继承和借用构造函数继承的结合,它既解决了属性共享问题,又保留了方法复用能力,是ES6之前最常用、最稳妥的继承方案。
为什么需要组合继承
单独用原型链继承,父类构造函数中的引用类型属性(比如数组、对象)会被所有子类实例共享;单独用借用构造函数(call/apply),虽然每个实例都有独立属性,但无法复用父类原型上的方法,造成内存浪费。组合继承正好取长补短:
- 用借用构造函数在子类构造函数中调用父类构造函数,确保每个实例拥有自己的一份属性(尤其是引用类型)
- 用原型链继承把父类原型对象赋给子类原型,让所有子类实例能共享父类的方法
标准写法与关键步骤
核心就两步:先“冒充”父类构造函数初始化实例属性,再把父类实例设为子类原型。注意顺序和细节:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
-
必须先调用父类构造函数(
SuperType.call(this, ...)),再设置原型(SubType.prototype = new SuperType()),否则原型上没方法 -
重置子类原型的 constructor,否则
instance.constructor会指向SuperType - 子类自己的方法要定义在原型上,且一定要在重设原型之后,避免被覆盖
一个可运行的完整示例
下面代码演示了如何正确实现组合继承,并验证其效果:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
SuperType.prototype.sayName = function() {
return this.name;
};
function SubType(name, age) {
// 第一步:借用构造函数 —— 继承实例属性(每个实例独有)
SuperType.call(this, name);
this.age = age;
}
// 第二步:原型链继承 —— 继承原型方法(所有实例共享)
SubType.prototype = new SuperType(); // 注意:这里不传参,仅用于建立原型链
SubType.prototype.constructor = SubType; // 修复 constructor 指向
// 添加子类特有方法
SubType.prototype.sayAge = function() {
return this.age;
};
// 验证
const instance1 = new SubType('Alice', 25);
const instance2 = new SubType('Bob', 30);
console.log(instance1.sayName()); // 'Alice'
console.log(instance2.sayAge()); // 30
instance1.colors.push('green');
console.log(instance2.colors); // ['red', 'blue'] —— 不受影响,说明属性未共享
常见陷阱与规避方式
实际编码中容易出错的地方:
-
忘记重设 constructor:会导致
instance.constructor === SuperType,影响 instanceof 判断和调试 -
在设置原型前定义子类方法:如
SubType.prototype.xxx = ...写在SubType.prototype = new SuperType()之前,这些方法会被新原型覆盖 - 给 new SuperType() 传参:原型链只用于继承方法,不应依赖构造函数副作用;参数应在 call 中传入
-
父类原型方法被意外覆盖:确保子类扩展原型时使用
SubType.prototype.xxx = function(){...}而非直接赋值整个 prototype 对象
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










