Skip to content

JS 实现继承的几种方式

原型链继承

示例代码

js
function Parent() {
  this.name = 'parent'
}
Parent.prototype.say = function() {
  console.log(this.name)
}

function Child() {
  this.name = 'Child'
}

Child.prototype = new Parent()

const child = new Child()
child.say() // 'Child'

特点

  • 子类共享父类的原型,父类修改,所有子类都受影响
  • 无法向父类的构造函数传递参数

构造函数继承继承

示例代码

js
function Parent(name) {
  this.name = name
  this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.say = function() {
  console.log(this.name)
}

function Child(name) {
  Parent.call(this, name)
}

const child = new Child('Child')
console.log(child.name) // 'Child'
console.log(child.colors) // ['red', 'blue', 'yellow']
child.say() // error: child.say is not a function

特点

  • 可以向父类传递参数
  • 仅能继承父类构造函数中的方法和属性,无法继承原型链上方法
  • 在有些使用场景下,父类构造函数会被多次调用,影响性能

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

示例代码

js
function Parent(name) {
  this.name = name
  this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.say = function() {
  console.log(this.name)
}
function Child(name, age) {
  Parent.call(this, name); // 继承构造函数中的属性
  this.age = age;
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

const child = new Child('Child', 18);
child.say(); // 'Child'
console.log(child.age) // 18
console.log(child.colors) // ['red', 'blue', 'yellow']

特点

  • 解决了原型链继承和构造函数继承的短板,是最常用的继承方式
  • 父类构造函数被调用了两次,一次在设置原型,另一次是在子类的构造函数中

原型式继承

示例代码

js
function createObject(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}

const parent = {
  name: 'Parent',
  say: function() {
    console.log(this.name);
  }
}
const child = createObject(parent)
console.log(child.name) // Parent
child.name = 'Child'
console.log(child.name) // Child
child.say() // Child

特点

  • 适合从已有对象创建新对象
  • 共享原型,修改可能影响其他对象(类型原型链继承,共享原型的问题依赖存在)

寄生式继承

代码示例

js
function createObject(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}
function createChild(parent) {
  const child = createObject(parent)
  child.say = function() {
    console.log(this.name);
  }
  return child
}

const parent = { name: 'Parent' }
const child = createChild(parent)
child.name = 'Child'
child.say() // Child

特点

  • 适合为对象添加额外的方法
  • 仍然存在原型共享的问题
  • 无法复用,每次创建对象都需要重新定义方法

组合寄生式

示例代码

js
function Parent(name) {
  this.name = name
  this.colors = ['red', 'blue', 'yellow']
}
Parent.prototype.say = function() {
  console.log(this.name)
}
function Child(name, age) {
  Parent.call(this.name)
  this.age = age
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
const child = new Child('Child', 18)
child.say() // Child
console.log(child.age) // 18
console.log(child.colors) // ['red', 'blue', 'yellow']

特点

  • 效率更高,避免了重复调用父类的构造函数,只在子类的构造函数中调用了一次
  • 是现代JavaScript中推荐的继承方式
  • 保持了原型链的正确性

ES6 类继承

示例代码

js
class Parent {
  constructor(name) {
    this.name = name
    this.colors = ['red', 'blue', 'yellow']
  }
  say() {
    console.log(this.name);
  }
}
class Child extends Parent {
  constructor(name, age) {
    super(name)
    this.age = age
  }
}
const child = new Child('Child', 18)
child.say() // Child
console.log(child.age) // 18
console.log(child.colors) // ['red', 'blue', 'yellow']

特点

  • 语法简单,易于理解,内置对 super 的支持,便于调用父类的方法
  • 旧版本的浏览器,需要转译处理

几种实现方式的对比

  • 原型链继承:简单,共享原型问题
  • 构造继承:可传参,但无法继承原型方法
  • 组合继承:可传参,可以继承原型方法, 但需要调用两次父类的构造函数
  • 寄生式继承:适合对象的拓展,但方法不可复用
  • 寄生组合式继承:效率高,推荐使用
  • ES6 class 继承:现在,简洁,推荐使用

推荐:优先使用 ES6 class 继承或者寄生组合式继承,理由是:语法清晰、性能较好、易于维护。