红宝石书的 "继承"

918 阅读3分钟

前言

继承是面向对象编程中讨论最多的话题,但却是日常业务开发中极易被忽略的一块内容。
本文围绕《Javascript高级程序设计第4版》面试高频问题进行知识点梳理。

原型链继承

基本思想: 函数Child没有使用默认显式原型,而是替换成函数Parent的实例。使Parent的实例可访问的属性和方法同样存在在函数Child实例的原型链上,从而实现了继承。

function Parent() {
  this.address = '火星'
}
Parent.prototype.getAddress = function() {
  console.log(this.address)
}
function Child() {

}
Child.prototype = new Parent()
let child1 = new Child('')
console.log(child1.getAddress()) // 火星

Child函数成功继承了函数Parent的属性。

问题:原型上引用类型被所有实例共享。

function Parent() {
  this.children = []
  this.address = '火星'
}
Parent.prototype.getChildren = function() {
  console.log(this.children)
}
function Child() {}
Child.prototype = new Parent()
let child1 = new Child()
let child2 = new Child()
child1.children.push('瑾行')
child2.getChildren() // 瑾行

构造函数继承

基本思想Child函数执行Parent函数,将当前函数执行上下文this指向child实例。

function Parent(name) {
  this.name = name
  this.address = '火星'
  this.children = []
  this.getChildren = function() {
    console.log(this.children)
  }
}
Parent.prototype.getAddress = function() {
  console.log(this.address)
}
function Child(name) {
  Parent.call(this, name)
}
let child1 = new Child('瑾行')
let child2 = new Child('七金')
child1.children.push('瑾行')
child2.getChildren() // []
child1.getAddress() // child1.getAddress is not a function
child2.getAddress() // child2.getAddress is not a function

解决了原型链继承引用类型共享的问题,且可传参。

问题:每创建一个实例都会创建一遍方法,且Parent原型链上的方法无法继承。

组合继承

基本思想:方法使用原型链继承,属性使用构造函数继承

function Parent(name) {
  this.name = name
  this.address = '火星'
  this.children = []
}
Parent.prototype.getChildren = function() {
  console.log(this.children)
}
Parent.prototype.getAddress = function() {
  console.log(this.address)
}

Child.prototype = new Parent()
Child.prototype.constructor = Child
function Child(name) {
  Parent.call(this, name)
}

let child1 = new Child('瑾行')
let child2 = new Child('七金')
child1.children.push('瑾行')
child2.getBanCreditCard() // []
child1.getAddress() // 火星
child2.getAddress() // 火星

解决了引用属性共享问题,且避免方法被重复创建的问题。

问题:基本问题解决了,但不难发现,我们会调用两次Parent构造函数,就是创建Child的原型的时候和创建实例的时候,还有待优化。

原型式继承

基本思想:使用Object.create的对parent参数对象浅复制,达到继承parent中的属性和方法。

Object.create的原生实现,将传入的对象作为创建对象的隐式原型。

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

原型式继承思想代码基本实现如下。

let parent = {
  name: '爸爸',
  address: '火星',
  children: []
}
let child1 = Object.create(parent)
let child2 = Object.create(parent)
child1.name = '瑾行'
child2.name = '七金'
child2.children.push('七金')
console.log(child1.name) // 瑾行
console.log(child2.name) // 七金
console.log(child1.children) // ['七金']

问题:引用属性会被实例共享,无法传递参数,具备与原型链继承一样的问题。

寄生式继承

基本思想:在原型式继承上增强浅复制能力,创建一个用于继承方法的函数。

function objectCreateEnhance(o) {
  let clone = Object.create(o)
  clone.say = function() {
    console.log('hello world')
  }
  return clone
}

问题:没有解决引用属性的问题,函数也同样需要重复创建。

寄生组合式继承

基本思想: 在组合继承的基础上进一步优化,将Parent显示原型浅复制赋值给Child显示原型,解决组合继承创建原型多调用Parent构造函数的问题。

function Parent(name) {
  this.name = name
  this.address = '火星'
  this.children = []
}
Parent.prototype.getChildren = function() {
  console.log(this.children)
}
Parent.prototype.getAddress = function() {
  console.log(this.address)
}
// 重点思想
function inheritPrototype(child,parent) {
  let prototype = Object.create(parent.prototype)
  child.prototype = prototype
  prototype.constructor = child
}
inheritPrototype(Child, Parent)
function Child(name) {
  Parent.call(this, name)
}

let child1 = new Child('瑾行')
let child2 = new Child('七金')
child1.children.push('瑾行')
child2.getBanCreditCard() // []
child1.getAddress() // 火星
child2.getAddress() // 火星

寄生组合式继承是目前最完美的继承方案:只调用一次Parent的构造函数,避免在Parent.prototype上添加多余的属性和方法,原型链保持不变,可以让child1child2合理共同继承。

extends

ES6新增的extends核心思想和寄生组合继承类似,相当于寄生组合继承的语法糖。 经过babel转义如下:

function _inherits(subClass, superClass) { 
    if (typeof superClass !== "function" && superClass !== null) { 
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    } 
    // subClass.prototype = superClass.prototype
    // subClass.prototype.constructor = subClass
    subClass.prototype = Object.create(superClass && superClass.prototype, { 
        constructor: { 
            value: subClass, 
            enumerable: false, 
            writable: true, 
            configurable: true 
        } 
    });
    // subClass.__proto__ = superClass
    if (superClass) {
      Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    }
}

与 ES5 的区别是子类的隐式原型指向了父类,作用是用于继承父级定义的静态属性(static 关键字定义)。