详解JavaScript中8 种不同的继承实现方式
作者:北辰alk
前言
在 JavaScript 中,继承是实现代码复用和构建复杂对象关系的重要机制。虽然 JavaScript 是一门基于原型的语言,不像传统面向对象语言那样有类的概念,但它提供了多种实现继承的方式。本文将详细介绍 JavaScript 中 8 种不同的继承实现方式,每种方式都会配有代码示例和详细解释,最后还会通过流程图比较各种继承方式的特点。
1. 原型链继承
原型链继承是 JavaScript 中最基本的继承方式,它利用原型让一个引用类型继承另一个引用类型的属性和方法。
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {
this.childName = 'Child';
}
// 关键步骤:将Child的原型指向Parent的实例
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()); // "Parent"
console.log(child1.childName); // "Child"
// 问题:引用类型的属性会被所有实例共享
child1.colors.push('black');
var child2 = new Child();
console.log(child2.colors); // ["red", "blue", "green", "black"]
特点:
- 简单易实现
- 父类新增原型方法/属性,子类都能访问到
- 无法实现多继承
- 来自原型对象的引用属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
2. 构造函数继承(经典继承)
通过在子类构造函数中调用父类构造函数实现继承。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
// 关键步骤:在子类构造函数中调用父类构造函数
Parent.call(this, name);
this.age = age;
}
var child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.name); // "Tom"
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]
// 问题:无法继承父类原型上的方法
console.log(child1.getName); // undefined
特点:
- 解决了原型链继承中引用类型共享的问题
- 可以在子类构造函数中向父类构造函数传参
- 可以实现多继承(call多个父类对象)
- 只能继承父类实例属性和方法,不能继承原型属性和方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3. 组合继承(最常用)
组合继承结合了原型链继承和构造函数继承的优点。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
// 构造函数继承 - 第二次调用Parent()
Parent.call(this, name);
this.age = age;
}
// 原型链继承 - 第一次调用Parent()
Child.prototype = new Parent();
// 修正constructor指向
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
var child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.name); // "Tom"
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
var child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]
console.log(child2.getName()); // "Jerry"
console.log(child2.getAge()); // 20
特点:
- 融合原型链继承和构造函数继承的优点
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
4. 原型式继承
借助原型可以基于已有对象创建新对象。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
ES5 规范化了原型式继承,新增了 Object.create() 方法:
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = Object.create(person, {
name: {
value: 'Linda'
}
});
yetAnotherPerson.friends.push('Barbie');
console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
特点:
- 不需要单独创建构造函数
- 本质是对给定对象执行浅复制
- 适用于不需要单独创建构造函数,但仍需要在对象间共享信息的场合
- 同原型链继承一样,包含引用类型的属性会被共享
5. 寄生式继承
创建一个仅用于封装继承过程的函数,在函数内部增强对象。
function createAnother(original) {
var clone = Object.create(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log('Hi');
};
return clone; // 返回这个对象
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "Hi"
特点:
- 基于原型式继承
- 增强了对象
- 无法实现函数复用
- 同原型式继承一样,引用类型属性会被共享
6. 寄生组合式继承(最理想)
通过借用构造函数继承属性,通过原型链混成形式继承方法。
function inheritPrototype(child, parent) {
var prototype = Object.create(parent.prototype); // 创建父类原型的副本
prototype.constructor = child; // 修正constructor指向
child.prototype = prototype; // 将副本赋值给子类原型
}
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键步骤:避免调用Parent构造函数,直接使用父类原型
inheritPrototype(Child, Parent);
Child.prototype.getAge = function() {
return this.age;
};
var child1 = new Child('Tom', 18);
var child2 = new Child('Jerry', 20);
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
console.log(child2.getName()); // "Jerry"
console.log(child2.getAge()); // 20
特点:
- 只调用一次父类构造函数
- 避免在子类原型上创建不必要的属性
- 原型链保持不变
- 能够正常使用 instanceof 和 isPrototypeOf
- 是引用类型最理想的继承方式
7. ES6 Class 继承
ES6 引入了 class 语法糖,使得继承更加清晰易读。
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
getName() {
return this.name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的constructor
this.age = age;
}
getAge() {
return this.age;
}
}
const child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
const child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]
特点:
- 语法更加清晰易读
- 底层实现仍然是基于原型
- 通过 extends 实现继承
- 子类必须在 constructor 中调用 super(),否则新建实例时会报错
- ES6 的继承机制完全不同,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this
8. 混入方式继承(多继承)
JavaScript 本身不支持多继承,但可以通过混入(Mixin)的方式实现类似功能。
function extend(target, ...sources) {
sources.forEach(source => {
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
// 支持Symbol属性
const symbols = Object.getOwnPropertySymbols(source);
symbols.forEach(symbol => {
target[symbol] = source[symbol];
});
});
return target;
}
const canEat = {
eat() {
console.log(`${this.name} is eating.`);
}
};
const canWalk = {
walk() {
console.log(`${this.name} is walking.`);
}
};
const canSwim = {
swim() {
console.log(`${this.name} is swimming.`);
}
};
function Person(name) {
this.name = name;
}
// 将多个mixin混入Person的原型
extend(Person.prototype, canEat, canWalk);
const person = new Person('John');
person.eat(); // "John is eating."
person.walk(); // "John is walking."
// person.swim(); // 报错,没有swim方法
function Fish(name) {
this.name = name;
}
extend(Fish.prototype, canEat, canSwim);
const fish = new Fish('Nemo');
fish.eat(); // "Nemo is eating."
fish.swim(); // "Nemo is swimming."
// fish.walk(); // 报错,没有walk方法
ES6 中可以使用 Object.assign 简化混入:
class Person {
constructor(name) {
this.name = name;
}
}
Object.assign(Person.prototype, canEat, canWalk);
class Fish {
constructor(name) {
this.name = name;
}
}
Object.assign(Fish.prototype, canEat, canSwim);
特点:
- 可以实现类似多继承的功能
- 灵活性强,可以按需组合功能
- 不是真正的继承,而是属性拷贝
- 可能会导致命名冲突
- 无法使用 instanceof 检查混入的功能
继承方式比较流程图

总结
JavaScript 提供了多种实现继承的方式,每种方式都有其适用场景和优缺点:
- 原型链继承:简单但引用类型属性会被共享
- 构造函数继承:可解决引用共享问题但无法继承原型方法
- 组合继承:最常用的继承方式,但会调用两次父类构造函数
- 原型式继承:适用于基于已有对象创建新对象
- 寄生式继承:增强对象但无法函数复用
- 寄生组合继承:最理想的继承方式,高效且完整
- ES6 Class继承:语法糖,底层仍是原型继承
- 混入方式:实现类似多继承的功能
在实际开发中,ES6 的 class 语法是最推荐的方式,它语法简洁,易于理解,且底层实现高效。对于需要兼容旧浏览器的项目,可以使用寄生组合式继承作为替代方案。
理解这些继承方式的原理和区别,有助于我们在不同场景下选择最合适的实现方式,写出更优雅、高效的 JavaScript 代码。
以上就是详解JavaScript中8 种不同的继承实现方式的详细内容,更多关于JavaScript实现继承的资料请关注脚本之家其它相关文章!
