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

一燈發表於2019-04-22

寫在前面

繼承的簡介

  “繼承”是JavaScript物件導向設計的重要一環,願你認真讀完本文,吃透繼承的概念。

繼承的核心

  在js中,繼承主要是依靠原型鏈實現的,原型鏈是通往繼承的必經之路。你可以參考《一張圖徹底KO原型鏈(prototype,__proto__)》這篇博文,相信它一定能很好地幫助你原型鏈。

1. 繼承方式一:原型鏈

1.1 介紹

  原型鏈是實現繼承最原始的模式,即通過prototype屬性實現繼承。

//父級-建構函式
function Father() {
  this.fatherProp = true
}

//父級-原型屬性
Father.prototype.getFatherValue = function() {
  return this.fatherProp
}

//子級-建構函式
function Son() {
  this.sonProp = false
}

//子級-原型屬性:繼承父級
//即__proto__指向父級的prototype
//若不理解請閱讀《一張圖徹底KO原型鏈(prototype,__proto__》
Son.prototype = new Father()

//子級-新增原型方法
Son.prototype.getSonValue = function() {
  return this.sonProp
}

//建立子級的例項物件
var son = new Son()
console.log(son.getFatherValue()) //true
複製程式碼

1.2 解析:son例項物件是如何找到getFatherValue()方法的呢?

  1. 首先在son物件自身中找。若物件自身沒找到
  2. 然後在Son.prototype中找。若Son.prototype中沒找到
  3. 繼續往上一層,Son.prototype.__proto__(Fater.prototype)
  4. 依次類推,直到找到需要的屬性或方法,或到達原型鏈頂端Object.prototype

  如果到最後都沒找到,會發生什麼呢?

//一個不存在的方法
console.log(son.getValue()) //ERROE:not a function
複製程式碼

1.3 注意事項

  1. 重寫父級原型鏈的方法或者新增父級原型鏈不存在的方法,必須在父級原型鏈程式碼之後。(這個很好理解,不放程式碼演示了)

  2. 通過原型鏈實現繼承後,不能再使用字面量的方式建立原型物件,因為會覆蓋原型鏈。

//子級-原型屬性:繼承父級
Son.prototype = new Father()

//不能像下面這樣,這樣會使得上面的程式碼無效
//因為這相當於重新建立了一個原型物件
Son.prototype = {
  getSonValue: function() {
    return this.sonProp
  }
}
複製程式碼

1.4 原型鏈實現繼承的弊端

   世間萬事萬物都不可能十全而十美,原型鏈雖然強大,但也存在缺陷。

  1. 原型鏈中引用型別的屬性會被所有例項共享的,即所有例項物件使用的是同一份資料,會相互影響。
  function Father() {
    this.arr = [1,2,3]
  }

  function Son() {
  }

  Son.prototype = new Father()

  var son1 = new Son()
  console.log(son1.arr)  //1,2,3

  var son2 = new Son()
  son2.arr.push(4)

  console.log(son2.arr)  //1,2,3,4
  console.log(son1.arr)  //1,2,3,4
複製程式碼
  1. 無法向父級建構函式傳參

2. 繼承方式二:借用建構函式

2.1 介紹

  方式一中引用型別帶來的問題可借用建構函式的方式解決。其核心思想是:在子級建構函式中呼叫父級建構函式。

  如何實現在一個建構函式中呼叫另一個函式?——call()和apply()

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

  function Son() {
    //call的第一個函式是this指向的物件,即建構函式的例項物件
    Father.call(this)

    /*上面程式碼等同於下面這段程式碼:
    (function() {
      this.arr = [1,2,3]
    }).call(this)
    */
  }

  var son1 = new Son()
  console.log(son1.arr)  //1,2,3

  var son2 = new Son()
  son2.arr.push(4)

  console.log(son2.arr)  //1,2,3,4
  console.log(son1.arr)  //1,2,3
複製程式碼
//解決傳參問題:
function Father(name) {
  this.name = name
}

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

var son1 = new Son("小名")
console.log(son1.name)    //小名

var son2 = new Son("一燈")
console.log(son2.name)    //一燈
複製程式碼

2.2 借用建構函式的缺陷

  這種方式是通過建構函式實現的,當然也把建構函式自身的問題帶過來了——破壞了複用性。因為每個例項都建立了一份副本。

3. 組合繼承

3.1 介紹

  組合繼承 = 原型鏈 + 借用建構函式。取其長避其短:共享的用原型鏈,各自的借用建構函式

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
}

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

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

var son2 = new Son("一燈", 24)
console.log(son2.arr) //1,2,3
son1.getName()        //一燈
son1.getAge()         //24
複製程式碼

3.2 解析

  1. 借用建構函式部分:

  Father.call(this, name)——name來自Father

  this.age = age; Son.prototype.constructor = Son——age來自Son

  1. 原型鏈部分:

  Father.prototype.getName——getName方法來自Father.prototype

  Son.prototype.getAge——getAge來自Son.prototype

後記

  關於繼承的後三種方式馬上推出,期待你的點贊&關注!

相關文章