在 JavaScript 中,继承是实现代码复用和构建对象体系的核心机制。由于 JS 是基于 原型(prototype) 的语言,其继承方式与传统面向对象语言(如 Java、C++)截然不同。
本文将系统解析 6 种继承方式,深入剖析每种方式的原理、优缺点,并提供可运行代码示例。
一、原型链继承(Prototype Chain Inheritance)
✅ 核心:子类原型指向父类实例
function SuperType() {
this.name = 'Super';
this.colors = ['red', 'blue'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType() {
this.subName = 'Sub';
}
// 继承:SubType 继承 SuperType
SubType.prototype = new SuperType();
const instance1 = new SubType();
const instance2 = new SubType();
instance1.colors.push('green');
console.log(instance2.colors); // ['red', 'blue', 'green'] — 被污染!
✅ 优点
- 父类方法可通过原型共享;
- 支持
instanceof和isPrototypeOf()。
❌ 缺点
- 引用类型被所有实例共享:
- 修改一个实例的引用属性会影响所有实例;
- 无法向父类构造函数传参:
// 无法在创建 SubType 时传递 name
⚠️ 结论:基本不可用,仅作理论理解。
二、借用构造函数(Constructor Stealing / Classic Inheritance)
✅ 核心:在子类构造函数中调用父类构造函数
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function SubType(name, age) {
// 继承属性并传参
SuperType.call(this, name);
this.age = age;
}
const instance1 = new SubType('Alice', 25);
const instance2 = new SubType('Bob', 30);
instance1.colors.push('green');
console.log(instance2.colors); // ['red', 'blue'] — 不受影响 ✅
✅ 优点
- ✅ 可向父类传递参数;
- ✅ 每个实例有独立的属性,避免引用共享问题。
❌ 缺点
- ❌ 无法继承父类原型上的方法:
instance1.sayName(); // ❌ TypeError: not a function - ❌ 所有方法都在构造函数中定义,无法复用,浪费内存。
⚠️ 结论:解决了属性共享问题,但破坏了方法复用。
三、组合继承(Combination Inheritance)—— 最常用
✅ 核心:构造函数 + 原型链
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
// 第二次调用 SuperType()
SuperType.call(this, name); // 继承属性
this.age = age;
}
// 第一次调用 SuperType()
SubType.prototype = new SuperType(); // 继承方法
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
};
const instance1 = new SubType('Alice', 25);
instance1.sayName(); // Alice
instance1.sayAge(); // 25
✅ 优点
- ✅ 支持传参;
- ✅ 属性独立,方法共享;
- ✅ 完整的继承链。
❌ 缺点
- ❌ 父类构造函数被调用两次:
new SuperType()设置原型;SuperType.call(this)初始化实例;
- ❌ 子类原型上存在不必要的属性(来自第一次调用):
console.log(SubType.prototype.name); // 'undefined'(但存在)
✅ 适用场景:ES6 之前的最通用继承方式。
四、原型式继承(Prototypal Inheritance)
✅ 核心:基于已有对象创建新对象
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
const person = {
name: 'Default',
friends: ['John', 'Jane']
};
const anotherPerson = object(person);
anotherPerson.name = 'Alice';
anotherPerson.friends.push('Bob');
const yetAnotherPerson = object(person);
console.log(yetAnotherPerson.friends);
// ['John', 'Jane', 'Bob'] — 被污染!❌
✅ ES5 内建方法:Object.create()
const alice = Object.create(person, {
name: { value: 'Alice' }
});
✅ 优点
- 语法简单,适合临时对象扩展。
❌ 缺点
- 与原型链继承相同:引用类型共享。
✅ 适用场景:对象间简单继承,非构造函数场景。
五、寄生式继承(Parasitic Inheritance)
✅ 核心:封装继承过程,增强对象
function createAnother(original) {
const clone = Object.create(original); // 原型式继承
// 增强:添加新方法
clone.sayHi = function() {
console.log('Hi!');
};
return clone;
}
const person = { name: 'Default' };
const newPerson = createAnother(person);
newPerson.sayHi(); // Hi!
✅ 优点
- 可以在不修改原对象的情况下扩展功能;
- 灵活。
❌ 缺点
- ❌ 方法在每次创建时都重新生成,无法复用;
- ❌ 类型识别困难。
✅ 适用场景:需要为对象动态添加功能的场景。
六、寄生组合继承(Parasitic Combination Inheritance)—— 最佳实践
✅ 核心:避免两次调用父类构造函数
function inheritPrototype(SubType, SuperType) {
const prototype = Object.create(SuperType.prototype); // 关键:使用父类原型的副本
prototype.constructor = SubType;
SubType.prototype = prototype;
}
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name); // 只调用一次
this.age = age;
}
// 核心:避免 new SuperType()
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
};
const instance = new SubType('Alice', 25);
instance.sayName(); // Alice
instance.sayAge(); // 25
✅ 优点
- ✅ 避免了组合继承中两次调用父类构造函数的问题;
- ✅ 方法共享,属性独立;
- ✅ 内存效率最高;
- ✅ 完整的继承链。
❌ 缺点
- 代码略复杂。
✅ 适用场景:ES6 之前的最优继承方案。
七、ES6 Class 继承(现代标准)
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
sayGrade() {
console.log(this.grade);
}
}
✅ 本质:
extends和super是寄生组合继承的语法糖,但更简洁、安全。
八、总结:继承方式对比
| 方式 | 传参 | 方法复用 | 引用共享 | 调用次数 | 推荐度 |
|---|---|---|---|---|---|
| 原型链 | ❌ | ✅ | ❌ | N/A | ⭐ |
| 借用构造 | ✅ | ❌ | ✅ | N/A | ⭐⭐ |
| 组合继承 | ✅ | ✅ | ✅ | 2次 | ⭐⭐⭐⭐ |
| 原型式 | ❌ | ✅ | ❌ | N/A | ⭐⭐ |
| 寄生式 | ✅ | ❌ | ✅ | N/A | ⭐⭐⭐ |
| 寄生组合 | ✅ | ✅ | ✅ | 1次 | ⭐⭐⭐⭐⭐ |
💡 结语
“寄生组合继承是 JavaScript 经典继承的巅峰之作。”
它完美解决了:
- 构造函数重复调用;
- 引用类型共享;
- 方法无法复用等问题。
而 class 的出现,让这一切变得极其简单。
掌握这些继承方式,不仅能应对面试,更能深入理解 JavaScript 的原型机制。