[JS]繼承的這6種方式!(下)

一燈發表於2019-04-23

本篇部落格接著繼承的這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__)

更多優質文章將持續更新,來關注一波……

相關文章