组合继承是ES6前最可靠的继承方案,通过“构造函数窃取+原型链连接”实现实例属性独立与原型方法复用:子类构造中调用Super.call(this)确保属性隔离并支持传参,再以new Super()赋值子类原型并修复constructor。

标准的对象式继承方案,核心是让子类实例既拥有独立的实例属性(不共享),又能访问父类原型上的方法(可复用)。单靠原型链或单靠构造函数窃取都不够——前者导致引用属性被所有实例共享,后者无法继承原型方法。“构造函数窃取 + 原型链连接”组合起来,就是业内常说的组合继承,也是ES6之前最可靠、最常用的继承实现。
为什么必须两者配合?
只用原型链(Sub.prototype = new Super()):父类构造函数中的引用类型属性(如数组、对象)会被所有子类实例共享,修改一个就影响全部;且无法向父类传参。
只用构造函数窃取(Super.call(this, ...)):能保证实例属性独立、支持传参,但子类实例拿不到父类原型上的方法(比如superMethod),得重复定义,浪费内存。
组合继承的标准写法
分两步走,缺一不可:
-
第一步:在子类构造函数中窃取父类构造逻辑
确保每个实例都有自己的一份属性副本,并支持初始化参数传递。 -
第二步:将子类原型指向父类的一个干净实例
不是直接赋值Super.prototype(会丢失constructor),而是用new Super()创建空实例并赋给Sub.prototype,再手动修复constructor指向。
关键细节不能漏
实际编码时容易踩坑,注意以下三点:
- 子类原型赋值后,必须重设
constructor:Sub.prototype.constructor = Sub;,否则instance.constructor会指向Super。 - 父类构造函数里不要定义方法(如
this.say = function(){}),否则每次调用call都会新建一份,违背复用原则;方法应统一挂到Super.prototype上。 - 父类实例化用于原型时,若构造函数有副作用(如发请求、改全局状态),需确保它接受无参调用,或改用
Object.create(Super.prototype)配合call模拟(即寄生式组合继承,更优但稍复杂)。
一个可运行的最小示例
(复制到控制台即可验证)
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
this.colors = ['brown']; // 引用类型,必须隔离
}
Animal.prototype.speak = function() {
return `${this.name} is ${this.age} years old.`;
};
// 子类
function Dog(name, age, breed) {
Animal.call(this, name, age); // ✅ 窃取:实例属性独立
this.breed = breed;
}
// ✅ 连接原型链:继承speak方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; // ✅ 修复constructor
Dog.prototype.bark = function() {
return 'Woof!';
};
const dog1 = new Dog('Leo', 3, 'Golden');
const dog2 = new Dog('Max', 5, 'Husky');
dog1.colors.push('gold');
console.log(dog1.colors); // ['brown', 'gold']
console.log(dog2.colors); // ['brown'] ← 不受影响
console.log(dog1.speak()); // "Leo is 3 years old."
console.log(dog1.bark()); // "Woof!"

















