实现继承的方式
JavaScript 中实现继承的方式
JavaScript 是基于原型链的语言,继承主要通过原型(prototype)来实现。随着 ES6 的普及,class 语法成为主流,但它本质上仍是基于原型的语法糖。下面详细介绍几种常见的继承实现方式。
1. 原型链继承
核心:将子类的原型对象指向父类的一个实例。
function Parent() {
this.names = ['Alice', 'Bob'];
}
Parent.prototype.getNames = function() {
return this.names;
};
function Child() {}
Child.prototype = new Parent(); // 继承
Child.prototype.constructor = Child; // 修复构造函数指向
const child1 = new Child();
child1.names.push('Charlie');
console.log(child1.getNames()); // ['Alice', 'Bob', 'Charlie']
const child2 = new Child();
console.log(child2.getNames()); // ['Alice', 'Bob', 'Charlie'] 问题:引用类型被共享
缺点:
- 所有子实例共享父类实例的引用类型属性(如
names数组)。 - 无法向父类构造函数传递参数。
2. 构造函数继承(盗用构造函数)
核心:在子类构造函数中通过 call / apply 调用父类构造函数。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
const child1 = new Child('Tom', 10);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
const child2 = new Child('Jerry', 8);
console.log(child2.colors); // ['red', 'blue'] 相互独立
child1.sayName(); // 报错:child1.sayName is not a function
缺点:
- 无法继承父类原型上的方法(只能继承实例属性)。
- 方法无法复用,每个实例都会创建相同的方法副本。
3. 组合继承(原型链 + 构造函数)
最常用的经典模式:用构造函数继承属性,用原型链继承方法。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
const child1 = new Child('Tom', 10);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
child1.sayName(); // 'Tom'
const child2 = new Child('Jerry', 8);
console.log(child2.colors); // ['red', 'blue']
缺点:
- 父类构造函数被调用了两次(一次在原型赋值,一次在
call),造成一定的性能浪费和原型上多余的属性。
4. 原型式继承(Object.create)
基于已有对象创建新对象,不涉及类。ES5 的 Object.create() 实现了这种方式。
const parent = {
name: 'Parent',
colors: ['red', 'blue'],
getName() {
return this.name;
}
};
const child1 = Object.create(parent);
child1.name = 'Child1';
child1.colors.push('green');
const child2 = Object.create(parent);
child2.name = 'Child2';
console.log(child2.colors); // ['red', 'blue', 'green'] 引用类型依然共享
缺点:
- 引用类型属性会被所有实例共享(类似原型链继承)。
- 适合简单对象的继承,不需要构造函数。
5. 寄生式继承
在原型式继承的基础上,增强对象(添加方法)。
function createAnother(original) {
const clone = Object.create(original); // 原型式继承
clone.sayHi = function() { // 添加新方法
console.log('Hi');
};
return clone;
}
const parent = { name: 'Parent', friends: ['A', 'B'] };
const child = createAnother(parent);
child.sayHi(); // 'Hi'
child.friends.push('C');
console.log(parent.friends); // ['A', 'B', 'C'] 共享引用
缺点:
- 方法在每个实例上重新创建,无法复用。
- 依然存在引用类型共享问题。
6. 寄生组合式继承(最理想的继承模式)
解决组合继承中父类构造函数被调用两次的问题,同时保持原型链的正确性。
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype); // 创建父类原型副本
prototype.constructor = child; // 修复 constructor
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 只调用一次 Parent
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {
console.log(this.age);
};
const child1 = new Child('Tom', 10);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
child1.sayName(); // 'Tom'
const child2 = new Child('Jerry', 8);
console.log(child2.colors); // ['red', 'blue']
优点:
- 只调用一次父类构造函数。
- 原型链干净,无多余属性。
- 是最理想的原型继承方式,ES5 中的推荐模式。
7. ES6 class 继承
使用 class 和 extends 关键字,底层实现近似寄生组合继承。
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
const child1 = new Child('Tom', 10);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
child1.sayName(); // 'Tom'
const child2 = new Child('Jerry', 8);
console.log(child2.colors); // ['red', 'blue']
注意:
super必须在访问this之前调用。- 类内定义的方法位于原型上,静态方法位于类自身。
extends甚至可用于继承内置类(如Array)。
8. 混入模式(Mixin)
多重继承的替代方案,将多个对象的属性混合到一个类中。
const sayMixin = {
sayHi() { console.log(`Hi, I'm ${this.name}`); }
};
const walkMixin = {
walk() { console.log(`${this.name} is walking`); }
};
class Person {
constructor(name) { this.name = name; }
}
Object.assign(Person.prototype, sayMixin, walkMixin);
const p = new Person('Alice');
p.sayHi(); // "Hi, I'm Alice"
p.walk(); // "Alice is walking"
总结
| 方式 | 核心特点 | 适用场景 |
|---|---|---|
| 原型链继承 | 简单,但引用类型共享 | 不推荐 |
| 构造函数继承 | 解决引用共享,无法继承原型方法 | 很少单独使用 |
| 组合继承 | 常用,但父类构造函数调用两次 | ES5 传统模式 |
| 原型式/寄生式继承 | 基于现有对象克隆,适合无构造函数场景 | 简单对象复用 |
| 寄生组合式继承 | 最理想的原型继承模式 | ES5 中推荐 |
ES6 class继承 | 语法简洁清晰,底层安全 | 现代开发首选 |
| 混入 (Mixin) | 实现多重继承效果 | 需要组合多个对象方法时 |
最佳实践:
在现代 JavaScript 开发中,优先使用 class + extends,清晰且易于维护。如果需要更细粒度的控制(如组合优于继承),可借助 Object.create() 或函数式混入。