# 继承

# 1. 概念

  • 继承是面向对象编程的基石。
  • 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法

# 1.1 继承的优点:

  • 提取公共代码,减少代码重复性
  • 提高代码可维护性
  • 让类与类之间产生了关系,是多态的前提

# 1.2 继承的类型:

  • 单继承
  • 多继承
  • 不同类继承同一个类
  • 多继承

# 2. js的继承

js本身最开始的设计只是为了实现网页提交表单时做个表单验证等简单功能
现在web端越来越重,导致js不得不持续更新完善自己,来支持实现越来越复杂的需求
所以经常会看到一些功能是有很多种实现方式的,例如异步解决方案,都是前辈们一步一步探索更优的解决方案的过程。

TIP

js不支持多继承
js只支持实现继承,不支持接口继承

下面是这张图是js继承的演变过程图(对理解js的继承,非常具有参考价值)

# 3. 原型链继承

# 3.1 基本思想

通过原型继承多个引用类型的属性和方法

# 3.2 代码实现

function Super(address){
  this.address = address
}
Super.prototype.sayAddress = function () {
  console.log(this.address)
}

function Sub() {

}
Sub.prototype = new Super('上海')

# 3.3 画图

# 优缺点

# 优点

  • 可以实现子类继承父类的实例属性和原型属性

# 缺点

  • 子类的原型是父类的实例,那么父类的原型与子类是共享的(引用关系),导致父类的原型修改后,子类也会被影响
  • 子类实例化时不能给父类的构造函数传参

# 4. 盗用构造函数

# 4.1 基本思想

为了解决原型包含引用值导致的继承问题,出现了盗用构造函数继承。
思想:在子类的构造函数中调用父类的构造函数

# 4.2 代码实现

function Super(address){
  this.address = address
}

function Sub(name) {
  Super.call(this, '上海')
  this.name = name
}

# 4.3 画图

# 4.4 优缺点

# 优点

不在利用原型链做继承,而是直接调用父类构造函数,实现实例属性继承

# 缺点

  • 子类不能访问父类原型上的方法,因为如果要继承,必须所有的类型都使用构造函数模式。
  • 也是构造函数模式的问题:必须在构造函数中定义方法,函数不能重用

# 5. 组合继承

# 5.1 基本思路

综合了原型链继承盗用构造函数继承
思路:使用原型链继承原型属性,使用构造函数继承实例属性

# 5.2 代码实现

function Super(address){
  this.address = address
}
Super.prototype.sayAddress = function () {
  console.log(this.address)
}

function Sub(name) {
  Super.call(this, '上海')
  this.name = name
}
Sub.prototype = new Super()

# 5.3 画图

# 5.4 优缺点

# 优点

  • 综合了 原型链继承 和 盗用构造函数继承 的优点
  • 保留了instanceofisPrototypeOf判断类型的识别能力

# 缺点

  • 原型链继承 和 盗用构造函数继承 重复调用了父类构造函数

TIP

组合模式是javascript中经常使用的继承模式,已经能够完成正常的继承。
但是重复调用构造函数是一种性能浪费

# 6. 原型式继承

# 6.1 基本思路

出发点: 即使不创建类型,也可以实现对象之间的信息共享
基本思路:构造一个临时构造函数,将传入的对象赋值给此临时构造函数的原型,然后返回一个临时构造函数的实例

# 6.2 代码实现

function object(o) {
  function F() {}
  F.prototype = o;
  return new F()
}

TIP

es6 通过 Object.create() 将此模式概念化了

# 6.3 画图

# 6.4 优缺点

# 优点

  • 不需要创建类型即可实现对象间信息共享

# 缺点

  • 共享的信息是引用关系,所以如果修改某一个,会彼此影响(同原型链继承类似)

# 7. 寄生式继承

# 7.1 基本思路

原型式继承的增强,创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

# 7.2 代码实现

function createAnother(o) {
  let clone = object(o) // 原型式继承
  clone.sayHi = function() {

  }
  return clone
}

# 8. 寄生式组合继承

# 8.1 基本思路

出发点:解决组合继承的效率问题 基本思路:不通过构造函数给子类原型赋值,而是取得父类原型的一个副本 寄生式继承(原型属性) + 盗用构造函数继承(实例属性)

# 8.2 代码实现

function inheritPrototype(sub, super) {
  // 对原型使用寄生模式,因为寄生模式最适合两个对象之间的信息共享
  let prototype = object(super.prototype) 
  prototype.constructor = sub
  sub.prototype = prototype
}

function Super(address){
  this.address = address
}
Super.prototype.sayAddress = function () {
  console.log(this.address)
}

function Sub(name) {
  // 盗用构造函数模式,实现实例属性的继承
  Super.call(this, '上海')
  this.name = name
}
inheritPrototype(Sub, Super)

# 8.3 画图

# 8.4 优缺点

# 优点

  • 最高效的实现了 实例属性 和 原型属性 的继承
  • instanceof操作符和 isPrototypeOf() 方法正常有效

TIP

寄生式组合继承可以 算是引用类型继承的最佳模式

# 总结

  • 原型式继承可以无须明确定义构造函数而实现继承,本质上是对 给定对象执行浅复制。这种操作的结果之后还可以再进一步增 强。
  • 与原型式继承紧密相关的是寄生式继承,即先基于一个对象创建 一个新对象,然后再增强这个新对象,最后返回新对象。这个模 式也被用在组合继承中,用于避免重复调用父类构造函数导致的 浪费。
  • 寄生组合继承被认为是实现基于类型继承的最有效方式