JavaScript不支持多继承,但可通过call/apply实现构造函数继承并手动合并原型链;需分别调用多个父类构造函数继承实例属性,并用Object.assign或getOwnPropertyDescriptor混入原型方法,最后修复constructor。

JavaScript本身不支持多继承,但可以通过call和apply手动调用多个父类的构造函数,实现类似“多继承”的属性和方法复用;同时需注意修复原型链,避免子类实例无法正确识别类型或丢失原型方法。
用call/apply模拟多继承(构造函数继承)
核心思路:在子类构造函数中,用call或apply分别执行多个父类构造函数,把它们的实例属性“嫁接”到当前子类实例上。
例如:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a sound");
};
function Flyable(height) {
this.flyHeight = height;
}
Flyable.prototype.fly = function() {
console.log("Flying at " + this.flyHeight + "m");
};
function Swimmable(speed) {
this.swimSpeed = speed;
}
Swimmable.prototype.swim = function() {
console.log("Swimming at " + this.swimSpeed + "km/h");
};
// Duck “继承” Animal、Flyable、Swimmable
function Duck(name, height, speed) {
Animal.call(this, name); // 继承实例属性:name
Flyable.call(this, height); // 继承实例属性:flyHeight
Swimmable.call(this, speed); // 继承实例属性:swimSpeed
}
const duck = new Duck("Donald", 100, 5);
console.log(duck.name); // "Donald"
console.log(duck.flyHeight); // 100
console.log(duck.swimSpeed); // 5
duck.speak(); // ❌ 报错!speak未定义 —— 原型链没连上
此时duck拥有三个父类的实例属性,但speak、fly、swim等原型方法都不可用 —— 因为Duck.prototype还是空的,默认指向Object.prototype。
立即学习“Java免费学习笔记(深入)”;
修复原型链(组合式原型继承)
不能直接让Duck.prototype = Animal.prototype(会污染Animal),也不能只选一个父类原型。合理做法是:创建一个干净的中间对象,依次混入多个父类的原型方法。
- 用
Object.create(null)或空对象作为基础原型 - 用
Object.assign()把各父类原型上的可枚举方法复制过去 - 最后把子类构造函数指回自身(修复
constructor)
继续上面的例子:
// 创建新原型对象,混入多个父类原型方法
Duck.prototype = Object.assign(
{},
Animal.prototype,
Flyable.prototype,
Swimmable.prototype
);
Duck.prototype.constructor = Duck; // 修复 constructor
// 现在 duck 可以调用所有方法
duck.speak(); // "Donald makes a sound"
duck.fly(); // "Flying at 100m"
duck.swim(); // "Swimming at 5km/h"
⚠️ 注意:Object.assign只复制**可枚举、自有属性**,不会复制constructor或__proto__相关逻辑,所以必须手动修复constructor。
更健壮的原型链处理(推荐封装工具函数)
手动Object.assign容易漏方法,尤其当父类原型上有Symbol属性或不可枚举方法时。更稳妥的方式是遍历所有原型属性(包括不可枚举的)并重新定义:
function inheritPrototypes(Child, ...Parents) {
const proto = {};
Parents.forEach(Parent => {
Object.getOwnPropertyNames(Parent.prototype).forEach(key => {
const desc = Object.getOwnPropertyDescriptor(Parent.prototype, key);
Object.defineProperty(proto, key, desc);
});
});
Child.prototype = proto;
Object.defineProperty(Child.prototype, 'constructor', {
value: Child,
enumerable: false,
writable: true,
configurable: true
});
}
// 使用
inheritPrototypes(Duck, Animal, Flyable, Swimmable);
这样能确保getOwnPropertyDescriptor捕获所有原型属性(含configurable:false或enumerable:false的方法),比Object.assign更完整。
注意事项与局限性
-
call/apply只能继承实例属性,无法自动继承原型方法 —— 必须额外处理原型链 - 多个父类有同名方法时,后混入的会覆盖前面的(如
Flyable.prototype.swim会覆盖Swimmable.prototype.swim) - 无法使用
instanceof准确判断多继承关系(duck instanceof Flyable === false),因为原型链上没有Flyable.prototype - 真实项目中更推荐组合(Composition)而非多继承:把
Flyable、Swimmable设计成可复用的行为对象,通过属性委托调用
不复杂但容易忽略细节 —— 关键是分开处理实例属性(call)和原型方法(显式合并),再小心修复constructor。


















