# 了解一下继承的方式?

  1. 多继承
  2. 传参
  3. 继承父类构造函数属性(实际上能传参就能继承)
  4. 继承父类原型属性(实例共享父类的原型,但可以实现多态),
  5. 复用父类构造函数(每次创建子类实例的时候不要重复调用父类构造函数)

个人感觉第 5 点非必要,因为复用则必然不可以多次传参,既然复用了就只能调用一次父类构造函数,所以传参也无意义了

因此我这里主要是讨论多继承,传参,原型属性这三个

# 原型链继承

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

特点:子类实例继承了 父类构造函数属性、父类原型属性

缺点:

  1. 继承单一,无法实现多继承

  2. 无法传参

  3. 丢失子类实例 constructor 属性

function Father() {}
function Child() {}
Child.prototype = new Father()
1
2
3

# 借用构造函数继承

核心:在子类构造函数中调用父类构造函数,改变父类构造函数的 this 指向

特点:

  1. 只继承了父类构造函数属性(结合 new 原理理解:改变 prototype,调用构造函数)
  2. 可以传参,多继承

缺点:没有继承父类原型属性

function Father() {}
function Child() {
  Father.call(this)
}
1
2
3
4

# 组合继承(前两种结合)

核心: 结合原型链继承和借用构造函数继承

特点: 多继承,传参,继承父类原型属性,父类构造属性

缺点:

  1. 丢失子类实例 constructor 属性
  2. 调用了两次构造函数
function Father() {}
function Child() {
  Father.call(this)
}
Child.prototype = new Father()
1
2
3
4
5

# 原型式继承(object.create 原理)

核心:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

特点:

  1. 只继承了父类原型属性
  2. 没有 new 调用,直接继承父类产生实例
  3. 没有子类构造函数,也就没有 constructor 丢失的问题

缺点:不能传参,不能多继承

function extendPrototype(obj) {
  // 类似原型链继承,但这里只是继承了父类原型属性
  // 其实就是利用一个空属性的构造函数来 除去 构造函数属性,只留下原型属性
  function F() {}
  F.prototype = obj
  return new F()
}
function Father() {}
var parent = new Father() //父类实例
var child = extendPrototype(parent)
1
2
3
4
5
6
7
8
9
10

# 寄生式继承(其实就是将一个新对象混入到父类中并返回)

核心:给原型式继承套个壳子而已

特点:

  1. 只继承了父类原型属性
  2. 不用 new 调用也可以达到相同的效果

缺点:不能传参,不能多继承

function extendPrototype(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}
function Father() {}
function Child() {
  //直接返回子类实例
  var parent = new Father()
  var child = extendPrototype(parent)
  //在这里给新对象增加属性和方法
  child.a = 'a'
  child.b = 'b'
  return child
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 寄生组合式继承(终极方案)

核心:子类原型是另一个只继承父类原型属性的实例,修复 constructor

寄生组合式继承的高效率体现在它只调用了一次superClass 构造函数,并且因此避免了在subClass.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf()

这是最成熟的方法,也是现在库实现的方法

特点:

  1. 继承了父类原型属性
  2. 可传参
  3. 多继承
function extend(subClass, superClass) {
  function extendPrototype(obj) {
    function F() {}
    F.prototype = obj
    return new F()
  }
  subClass.prototype = extendPrototype(superClass.prototype) //继承父类原型 constructor被覆盖
  subClass.prototype.construtor = subClass //修复constructor
}
function Father() {}
function Child() {
  Father.call(this)
}
extend(Child, Father)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# ES6 类继承 extends

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法

extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样

function extend(subClass, superClass) {
  // 创建对象,创建父类原型的一个副本
  // 指定对象,将新创建的对象赋值给子类的原型
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  })
  // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  if (superClass) {
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subClass, superClass)
      : (subClass.__proto__ = superClass)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 面试点

1、函数声明和类声明的区别

函数声明会提升,类声明不会。首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个 ReferenceError。

let p = new Rectangle()
// ReferenceError

class Rectangle {}
1
2
3
4

2、ES5 继承和 ES6 继承的区别

  • ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.call(this)).
  • ES6 的继承有所不同,实质上是先创建父类的实例对象 this,然后再用子类的构造函数修改 this。因为子类没有自己的 this 对象,所以必须先调用父类的 super()方法,否则新建实例报错。

# 参考

JavaScript 常用八种继承方案 (opens new window)

JS 原型链与继承别再被问倒了 (opens new window)

最近更新: 4 小时前