一、继承方式的演进之路
1. 原型链继承(Prototypal Inheritance)
function Parent() { this.parentProp = 'Parent' }
Parent.prototype.getParent = () => this.parentProp;
function Child() { this.childProp = 'Child' }
Child.prototype = new Parent(); // 核心代码
const child = new Child();
console.log(child.getParent()); // 'Parent'
核心问题: • 引用类型属性共享(所有实例共享父类实例属性) • 无法向父类构造函数传参
2. 借用构造函数继承(Constructor Stealing)
function Parent(name) { this.name = name }
function Child(name) {
Parent.call(this, name); // 核心代码
this.type = 'Child';
}
const child = new Child('Tom');
console.log(child.name); // 'Tom'
解决的问题: • 实例属性独立 • 支持参数传递
新问题: • 无法复用原型方法 • 父类方法需在构造函数中定义
3. 组合继承(Combination Inheritance)
function Parent(name) {
this.name = name;
this.colors = ['red'];
}
Parent.prototype.sayName = function() { console.log(this.name) };
function Child(name) {
Parent.call(this, name); // 第二次调用父类构造函数
}
Child.prototype = new Parent(); // 第一次调用父类构造函数
Child.prototype.constructor = Child;
const child = new Child('Tom');
child.colors.push('blue');
优化点: • 实例属性独立 • 原型方法复用
遗留问题: • 父类构造函数被调用两次 • 原型链上存在冗余属性
二、现代继承方案
1. 寄生组合式继承(Parasitic Combination)
function inheritPrototype(child, parent) {
const proto = Object.create(parent.prototype);
proto.constructor = child;
child.prototype = proto;
}
function Parent(name) { this.name = name }
Parent.prototype.sayName = function() { console.log(this.name) };
function Child(name) {
Parent.call(this, name);
}
inheritPrototype(Child, Parent); // 核心优化
核心优势: • 只调用一次父类构造函数 • 原型链干净无冗余
2. ES6 Class继承
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name) {
super(name); // 必须首先调用
this.type = 'Child';
}
static staticMethod() {
return 'Static method';
}
}
五大优势:
- 语法简洁:
extends和super关键字 - 完善的静态方法继承
- 内置
new.target等元属性支持 - 更好的错误检查机制
- 原生支持内置类继承
三、突破性能力:内置类继承
传统方式的局限
function MyArray() {
Array.call(this); // 无法正确初始化
}
MyArray.prototype = Object.create(Array.prototype);
const arr = new MyArray(1,2,3);
console.log(arr.length); // 0 ❌
Class继承的突破
class MyArray extends Array {
sum() {
return this.reduce((a,b) => a + b, 0);
}
}
const arr = new MyArray(1,2,3);
console.log(arr.length); // 3 ✅
console.log(arr.sum()); // 6 ✅
原理差异:
• class继承通过[[Constructor]]内部方法正确处理内置类的特殊初始化逻辑
• 传统方式无法触发内置类的完整初始化流程
四、最佳实践选择
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 原型链继承 | 简单对象继承 | 实现简单 | 无法隔离实例属性 |
| 组合继承 | 需要兼容ES5的环境 | 浏览器兼容性好 | 存在冗余属性 |
| 寄生组合式继承 | 需要精细控制原型链 | 最理想的ES5继承方案 | 实现相对复杂 |
| Class继承 | ES6+环境 | 语法简洁、功能完善 | 需要转译兼容旧浏览器 |
现代开发建议:
- 优先使用
class语法 - 需要兼容旧环境时采用寄生组合式继承
- 避免直接操作
__proto__属性 - 使用
instanceof和isPrototypeOf进行类型检查
理解JavaScript继承的演进过程,能帮助我们更好地处理遗留代码,同时写出更符合现代标准的优雅实现。随着ES6的广泛支持,Class语法已成为继承方案的首选,但其背后的原型链机制仍然是每个前端开发者必须掌握的核心知识。