Skip to main content

实现继承的方式

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 继承

使用 classextends 关键字,底层实现近似寄生组合继承。

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() 或函数式混入。