文章目录

一、JavaScript 继承的基本概念二、原型链继承1. 实现方式2. 存在的问题

三、构造函数继承1. 实现方式2. 解决的问题3. 存在的问题

四、组合继承(构造函数 + 原型链)1. 实现方式2. 解决的问题3. 存在的问题

五、寄生组合继承(推荐方案)1. 实现方式2. 优点

六、ES6 `class` 继承(最现代化的方式)1. 实现方式2. 优点

七、总结

JavaScript 是一门基于原型的语言,它的继承机制与传统的基于类的面向对象编程(如 Java、C++)有所不同。尽管 ES6 引入了 class 语法,但本质上仍然是基于原型链的继承。本文将详细介绍 JavaScript 继承的几种实现方式,包括原型链继承、构造函数继承、组合继承、寄生组合继承,以及 ES6 class 继承,并分析它们的优缺点和适用场景。

一、JavaScript 继承的基本概念

在 JavaScript 中,每个对象都有一个原型(prototype),新创建的对象可以从原型对象继承属性和方法。继承的核心思想是:子类对象可以访问父类的属性和方法,从而实现代码复用。

JavaScript 主要提供了以下几种继承方式:

原型链继承构造函数继承组合继承(构造函数 + 原型链)寄生组合继承(最优方案)ES6 class 继承

接下来,我们将逐一介绍这些继承方式的实现原理、代码示例以及优缺点。

二、原型链继承

1. 实现方式

原型链继承的核心思想是让子类的 prototype 指向父类的实例,这样子类就能访问父类的方法和属性。

function Parent() {

this.name = "Parent";

this.colors = ["red", "blue", "green"];

}

Parent.prototype.getName = function() {

return this.name;

};

function Child() {}

Child.prototype = new Parent(); // 关键点:让子类的 prototype 指向父类实例

const child1 = new Child();

console.log(child1.getName()); // Parent

console.log(child1.colors); // ["red", "blue", "green"]

child1.colors.push("yellow");

const child2 = new Child();

console.log(child2.colors); // ["red", "blue", "green", "yellow"] (被修改了)

2. 存在的问题

子类实例共享父类的引用属性:修改 child1.colors 之后,child2.colors 也会受到影响,因为 colors 是父类实例的属性,所有子类实例都共享同一个对象。无法向父类构造函数传参:创建子类实例时,无法向 Parent 传递参数。

三、构造函数继承

1. 实现方式

通过在子类的构造函数中调用父类构造函数,并使用 call() 或 apply() 绑定 this,从而避免原型链继承的问题。

function Parent(name) {

this.name = name;

this.colors = ["red", "blue", "green"];

}

function Child(name) {

Parent.call(this, name); // 关键点:在子类构造函数中调用父类构造函数

}

const child1 = new Child("Child1");

const child2 = new Child("Child2");

child1.colors.push("yellow");

console.log(child1.colors); // ["red", "blue", "green", "yellow"]

console.log(child2.colors); // ["red", "blue", "green"] (不会被修改)

2. 解决的问题

✅ 避免了引用属性共享问题(子类实例有自己的 colors) ✅ 支持向父类构造函数传参

3. 存在的问题

方法不能复用:父类的方法必须在每个子类实例上都创建一次,而不是共享在 prototype 上。

四、组合继承(构造函数 + 原型链)

1. 实现方式

组合继承结合了构造函数继承和原型链继承的优点。

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;

}

Child.prototype = new Parent(); // 继承父类方法

Child.prototype.constructor = Child; // 修正 constructor 指向

const child1 = new Child("Child1", 18);

console.log(child1.getName()); // Child1

console.log(child1.colors); // ["red", "blue", "green"]

2. 解决的问题

✅ 避免了引用属性共享问题 ✅ 支持传参 ✅ 方法可以复用

3. 存在的问题

父类构造函数被调用了两次(一次在 Parent.call(this, name),一次在 new Parent())。

五、寄生组合继承(推荐方案)

1. 实现方式

寄生组合继承优化了组合继承中的 new Parent(),避免了父类构造函数的重复调用。

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;

}

Child.prototype = Object.create(Parent.prototype); // 继承方法,但不会执行 Parent 构造函数

Child.prototype.constructor = Child; // 修正 constructor 指向

const child1 = new Child("Child1", 18);

console.log(child1.getName()); // Child1

console.log(child1.colors); // ["red", "blue", "green"]

2. 优点

✅ 避免引用属性共享问题 ✅ 支持传参 ✅ 方法可以复用 ✅ 避免了 new Parent() 造成的二次调用

六、ES6 class 继承(最现代化的方式)

1. 实现方式

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); // 继承父类属性

this.age = age;

}

}

const child1 = new Child("Child1", 18);

console.log(child1.getName()); // Child1

console.log(child1.colors); // ["red", "blue", "green"]

2. 优点

✅ 语法简洁,易读易写 ✅ 继承逻辑清晰 ✅ super() 关键字提供更清晰的继承机制

七、总结

继承方式主要特点优点缺点原型链继承让子类 prototype 指向父类实例方法共享共享引用属性构造函数继承使用 call 调用父类解决共享问题,可传参方法无法复用组合继承构造函数 + 原型链解决共享问题调用两次父类构造函数寄生组合继承最优 ES5 方案解决所有问题代码略复杂ES6 class 继承最现代化方案语法简洁,推荐使用仅限 ES6+

在现代 JavaScript 开发中,建议 优先使用 ES6 class 继承,但在需要兼容旧浏览器时,可以使用 寄生组合继承。

推荐:

JavaScriptreactvue