Skip to content

继承

提示

继承是面向对象编程中的核心概念,主要用于提升代码复用性。本文总结了 JavaScript 中常见的继承方式及其优缺点。

在 JavaScript 中,继承主要依赖原型链来实现。

1. 原型链继承

核心思想:将父类的实例作为子类的原型。

js
function SuperType() {
  this.property = 'Super'
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.getSuperValue = function () {
  return this.property
}

function SubType() {
  this.subproperty = false
}

// 关键点:重写子类原型,指向父类实例
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
  return this.subproperty
}

const instance1 = new SubType()
instance1.colors.push('black')

const instance2 = new SubType()
console.log(instance2.colors) // ['red', 'blue', 'green', 'black']

缺点

  1. 引用属性共享:父类实例属性变成子类原型属性,导致所有子类实例共享引用类型数据(如数组),修改一个会影响所有。
  2. 无法传参:创建子类实例时,无法向父类构造函数传参。

2. 借用构造函数继承 (经典继承)

核心思想:在子类构造函数中调用父类构造函数。

js
function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

function SubType(name) {
  // 继承 SuperType,并传参
  SuperType.call(this, name)
}

const instance1 = new SubType('fengfeng')
instance1.colors.push('black')
console.log(instance1.name) // 'fengfeng'
console.log(instance1.colors) // ['red', 'blue', 'green', 'black']

const instance2 = new SubType('fengfeng')
console.log(instance2.colors) // ['red', 'blue', 'green']

优点

  1. 解决了引用属性共享问题。
  2. 可以向父类传参。

缺点

  1. 方法无法复用:方法都在构造函数中定义,每次创建实例都会重新创建方法。
  2. 无法继承原型方法:只能继承父类实例属性/方法,无法访问父类原型上的方法。

3. 组合继承 (伪经典继承)

核心思想:结合原型链继承和借用构造函数继承。使用原型链继承方法,使用构造函数继承属性。

js
function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

function SubType(name, age) {
  // 继承属性 (第二次调用父类构造函数)
  SuperType.call(this, name)
  this.age = age
}

// 继承方法 (第一次调用父类构造函数)
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType // 修复 constructor 指向

SubType.prototype.sayAge = function () {
  console.log(this.age)
}

优点

  1. 融合了前两者的优点,是 ES6 之前最常用的继承模式。
  2. 保留了 instanceofisPrototypeOf() 的识别能力。

缺点

  1. 调用两次父类构造函数:一次在创建子类原型时,一次在子类构造函数内部。导致子类原型上产生多余的父类实例属性。

4. 原型式继承

核心思想:不自定义类型,而是借助一个临时构造函数,将传入的对象作为其原型。ES5 的 Object.create() 规范了这种方式。

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

// 使用
const person = {
  name: 'person',
  colors: ['red', 'blue', 'green'],
}

const instance1 = object(person) // 或 Object.create(person)
instance1.colors.push('black')

const instance2 = object(person)
console.log(instance2.colors) // ['red', 'blue', 'green', 'black']

缺点

  1. 与原型链继承类似,引用类型属性会被共享。

5. 寄生式继承

核心思想:创建一个仅用于封装继承过程的函数,在内部以某种方式增强对象,最后返回对象。

js
function createAnother(original) {
  const clone = Object.create(original) // 创建新对象
  clone.sayHi = function () {
    // 增强对象
    console.log('hi')
  }
  return clone // 返回对象
}

缺点

  1. 与借用构造函数类似,方法无法复用。

6. 寄生组合式继承 (最佳实践)

核心思想:通过借用构造函数继承属性,但使用寄生式继承来继承父类原型(而非调用父类构造函数)。

js
function inheritPrototype(subType, superType) {
  // 1. 创建父类原型的副本
  const prototype = Object.create(superType.prototype)
  // 2. 修正 constructor 指向
  prototype.constructor = subType
  // 3. 将副本赋值给子类原型
  subType.prototype = prototype
}

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name) // 继承属性
  this.age = age
}

inheritPrototype(SubType, SuperType) // 继承方法

SubType.prototype.sayAge = function () {
  console.log(this.age)
}

优点

  1. 只调用一次父类构造函数。
  2. 避免了在子类原型上创建不必要的属性。
  3. 原型链保持不变。
  4. 这是引用类型最理想的继承范式

7. ES6 类继承 (extends)

ES6 引入了 class 关键字,但其本质依然是基于原型的语法糖。

js
class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }

  toString() {
    return `(${this.x}, ${this.y})`
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y) // 调用父类构造函数,必须在 this 之前
    this.color = color
  }

  toString() {
    return `${this.color} ${super.toString()}` // 调用父类方法
  }
}

ES5 vs ES6 继承的区别

  1. ES5:先创建子类实例对象 this,再将父类的方法添加到 this 上(Parent.call(this))。
  2. ES6:先将父类实例对象的属性和方法加到 this 上(所以必须先调用 super()),然后再用子类构造函数修改 this

Babel 编译原理简析

Babel 将 ES6 的 extends 编译为类似于寄生组合式继承的代码,并处理了 __proto__ 的指向,使得子类构造函数也能继承父类构造函数的静态属性。

js
// 核心逻辑模拟
function _inherits(subClass, superClass) {
  // 继承原型方法
  subClass.prototype = Object.create(superClass.prototype)
  subClass.prototype.constructor = subClass

  // 继承静态属性 (ES6 特性)
  // subClass.__proto__ = superClass
  Object.setPrototypeOf(subClass, superClass)
}

如有转载或 CV 的请标注本站原文地址