【JS系列】继承的这6种方式!(下)

897 阅读3分钟

本篇博客接着继承的这6种方式!(上),继续介绍你想知道的后三种继承方式,尤其是最理想的寄生组合式继承。

4. 原型式继承

4.1 介绍

  原型式继承的基本思想:

  • 1个基础对象
  • 1个新对象,把基础对象作为原型对象
  • 新对象创建实例
//基础对象
var person = {
  name: "一灯",
  arr: [1,2,3]
}

//Object.create()创建新对象,传入基础对象
var son1 = Object.create(person)
son1.name = "AAA"
son1.arr.push(4)
console.log(son1.name)    //AAA

var son2 = Object.create(person)
son2.name = "BBB"
console.log(son2.name)    //BBB
console.log(son2.arr)     //1,2,3,4,引用类型问题依然存在

  当然你也可以使用Object.create()的第二个参数传添加对象属性

var person = {
  name: "一灯",
  arr: [1,2,3]
}

var son1 = Object.create(person, {
  name: {
    value: "AAA"
  }
})
son1.arr.push(4)
console.log(son1.name)    //AAA

var son2 = Object.create(person, {
  name: {
    value: "BBB"
  }
})
console.log(son2.name)    //BBB
console.log(son2.arr)     //1,2,3,4

4.2 优劣分析

  • 原型式继承解决了原型链无法传参的问题,并且无需使用构造函数(避免了构造函数的问题)。因此在没必要使用构造函数时可以采用这种方法。

  • 引用类型问题依旧存在

5. 寄生式继承

5.1 介绍

  寄生式继承可以理解为是原型式继承的增强。在原型式继承中我们创建了一个新对象,寄生式继承便是在新对象中添加方法,以增强对象。

var person = {
  name: "一灯",
  arr: [1,2,3]
}

//增强对象
function increase(obj, prop) {
  var object = Object.create(obj,prop)
  object.getName =  function() {
    console.log(this.name)
  }
  return object
}

var son1 = increase(person, {
  name: {
    value: "AAA"
  }
})
son1.arr.push(4)
console.log(son1.name)    //AAA
son1.getName()            //AAA

var son2 = increase(person, {
  name: {
    value: "BBB"
  }
})
console.log(son2.name)    //BBB
console.log(son2.arr)     //1,2,3,4
son2.getName()            //BBB

5.2 缺陷

  寄生式继承类似于构造函数,每个实例对象都有一个副本——破坏了复用性

6. 寄生组合式继承——大招来了

6.1 介绍

  在继承的这6种方式!(上)中讲到的组合式继承比较常用的一种方式,然而这种方式还是存在一个问题——父级构造函数调用了两次(可以到上篇博文查看代码)。一次在创建子级原型对象,另一次在子级构造函数内部。

  对于精益求精的码农,当然不能容忍,因此诞生了寄生组合式继承,其核心思想是:

  • 组合式:子级的prototype继承父级的prototype——通过new Father()
  • 寄生组合式:子级的prototype继承父级的prototype——通过赋值
function inheritPrototype(Son, Father) {
  //创建一个Father.prototype的副本
  var prototype = Object.create(Father.prototype)
  /*下面这句代码的很多资料的描述感觉不太清晰,我的理解:
  *1、Father.prototype的作用是赋值给Son.prototype
  *2、如果没有下面这条语句:Son.prototype.constructor == Father构造函数
  *3、因此需要更改Son.prototype.constructor,模拟new Father()的过程
  */
  prototype.constructor = Son
  //把Father.prototype赋值给 Son.prototype
  Son.prototype = prototype
}

function Father(name) {
  this.name = name
  this.arr = [1,2,3]
}

Father.prototype.getName = function() {
  console.log(this.name)
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

inheritPrototype(Son, Father)

Son.prototype.getAge = function() {
  console.log(this.age)
}

var son1 = new Son("AAA", 23)
son1.getName()            //AAA
son1.getAge()             //23
son1.arr.push(4)          
console.log(son1.arr)     //1,2,3,4

var son2 = new Son("BBB", 24)
son2.getName()            //BBB
son2.getAge()             //24
console.log(son2.arr)     //1,2,3

6.2 优劣分析

  • 寄生组合式继承对于引用类型的继承来说是最理想的继承方式:避免了应用类型问题,并且只调用一次父级构造函数。

  • 要说其缺点就是比其他方式更为复杂一些

总结

  1. JS共有6种方式实现继承:
  • 原型链:最原始的继承方式(引用类型值相互影响、无法向父级构造函数传参)
  • 借用构造函数:解决原型链的问题,但破坏了复用性
  • 组合式:原型链+借用构造函数(取长避短),但调用了两次父级构造函数
  • 原生式:解决原型链传参问题,并且无需使用构造函数,但也存在引用类型问题
  • 寄生式:原生式的增强
  • 寄生组合式:寄生式+组合式,解决了各种问题,只是代码稍微复杂
  1. 理解原型/原型链对理解继承帮助甚大,强烈建议先弄得JS原型——一张图彻底KO原型链(prototype,__proto__)

更多优质文章将持续更新,来关注一波……