JS中的繼承 —— 簡單理解

回想起剛入門JS的時候,初次接觸JS原型繼承,令我頭大,心想啊,為什么要把繼承搞得這么復雜。隨著時間推移,學習源碼,漸漸的對JS繼承有了理解。 以下內(nèi)容,如有錯誤,請指正。(因為全是自己的理解)

1. 為什么JS繼承是基于原型繼承?

要解答這個問題,首先我們要了解JS的歷史。隨便學什么語言知識,我個人覺得都要去了解它的歷史,通過歷史總結經(jīng)驗。我簡單說一些,想詳細了解可自行google。
JS的出現(xiàn),是為了解決瀏覽器無法交互的缺陷。它的初衷就是就是滿足用戶交互,所以把這個語言設計成了腳本語言(想想為什么ES6以前都是用var來聲明變量)。JavaScriptJava完全是兩門語言(雖然實現(xiàn)JS時抄了一些Java的思想),JS根本就不是一門面向?qū)ο蟮恼Z言,所以在JS中沒有的概念(ES6中實現(xiàn)了類,也只是原型繼承的語法糖)。所以JS的作者Brendan Eich使用了原型繼承,來彌補類的缺陷,并且讓JS看起來好像是一門面向?qū)ο缶幊痰恼Z言。

2. 什么是繼承

繼承是面向?qū)ο笳Z言的特性之一,子類能獲得基類中的一些方法 (兒子繼承爸爸的基因)。JS中,基類能被繼承的方法都在基類(構造函數(shù))的prototype原型中。

3. JS如何實現(xiàn)原型繼承的?

首先你得知道JS的構造函數(shù),比如常用的 Array(), Date(), Number(), 它們都是構造函數(shù),其實就是普通的一個函數(shù),大家為了區(qū)分構造函數(shù)和普通函數(shù), 使用了命名的方式 —— 構造函數(shù)使用大寫字母開頭命名, 普通函數(shù)用小寫字母開頭。構造函數(shù)在JS中就相當于 , 但切記,JS中沒有類這個概念。我們先寫一個構造函數(shù),看看它的結構是怎樣的

  function Parent(name) {
    this.name = name
    this.say = function () {  // 相當于private方法
      console.log('這是構造函數(shù)的方法')
    }

  }
  Parent.prototype.move = function () {  // 相當于public方法
    console.log('這是構造函數(shù)的原型上的方法')
  }
  var parent = new Parent('lion')
  console.dir(parent)

打印Parent的實例看看

parent實例

可以看到,move方法在實例的__proto__中,我們要實現(xiàn)繼承, 那就要把這個__proto__中的方法賦給子類,但是不能直接使用__proto__變量,因為它是瀏覽器實現(xiàn)的方便程序員查看繼承的一個變量,官方建議不要直接使用這個變量,更不要去更改它的值,會造成原型鏈上問題。那我們可以這樣來實現(xiàn): ??

  function Son() {}
  var temp = Object.create(Parent.prototype)
  temp.constructor = Son
  Son.prototype = temp
  var son = new Son()

  console.dir(son)

打印子類實例看看


son實例

這樣便實現(xiàn)了繼承。

4. 思路
  • 1.已知需要繼承的方法在構造函數(shù)的prototype屬性中,我們就把這些方法取出來,通過Object.create(parentProto) -》用基類的原型創(chuàng)建一個實例,這個實例對象上有個__proto__屬性,里面是基類的prototype中的方法
    1. 需要繼承的方法取出來之后,要放入子類的prototype屬性中,并且函數(shù)的prototype.constructor指向函數(shù)本身,所以還需要在基類原型構造出的實例上添加子類的構造函數(shù),即上面代碼的temp.constructor = Son

這兩步就實現(xiàn)了繼承??吹竭@里可能還有點懵,我非常能理解,但是當你明白__proto__prototype的作用之后,你回頭看看這個思路以及上面的代碼, 就恍然大悟了。

5. 解釋prototype ,__proto__

prototype對象屬性是JS為了模仿面向?qū)ο缶幊潭鴮崿F(xiàn)的。每個函數(shù)都有prototype屬性。默認的prototype對象是這樣子的

prototype對象包含了兩個屬性:constructor__proto__。 其中constructor指向函數(shù)本身,可以看到constructor中還有prototype屬性,這個prototype 中的constructor又指向函數(shù)本身, 所以有的同學納悶兒為什么這個屬性點不完,原因就在這里,循環(huán)引用。
還有個__proto__對象,它就是原型的基類,每個對象都有__proto__屬性,MDN上是這樣子解釋的:

Object.prototype__proto__ 屬性是一個訪問器屬性(一個getter函數(shù)和一個setter函數(shù)), 暴露了通過它訪問的對象的內(nèi)部[[Prototype]] (一個對象或 null)。
使用__proto__是有爭議的,也不鼓勵使用它。因為它從來沒有被包括在EcmaScript語言規(guī)范中,但是現(xiàn)代瀏覽器都實現(xiàn)了它。__proto__屬性已在ECMAScript 6語言規(guī)范中標準化,用于確保Web瀏覽器的兼容性,因此它未來將被支持。它已被不推薦使用, 現(xiàn)在更推薦使用Object.getPrototypeOf/Reflect.getPrototypeOfObject.setPrototypeOf/Reflect.setPrototypeOf(盡管如此,設置對象的[[Prototype]]是一個緩慢的操作,如果性能是一個問題,應該避免)。

通俗的說,__proto__并不是JS規(guī)定實現(xiàn)的,而是瀏覽器為了方便開發(fā)人員調(diào)試而實現(xiàn)的。除了Object.create(null)創(chuàng)建的實例對象沒有__proto__屬性外,其他所有對象都有這個屬性,包括構造函數(shù)(函數(shù)是對象的子類),這個屬性指向了對象的基類。

6. 總結原型繼承

從構造函數(shù)實例化出一個對象后,這個對象的__proto__屬性指向了構造函數(shù)的prototype屬性, 構造函數(shù)的prototype屬性中也有個__proto__屬性(因為prototype是對象,每個對象都有__proto__屬性),這個__proto__屬性又指向了基類的prototype屬性(因為這個__proto__相當于基類的實例對象),如此循環(huán),直到最后__proto__指向Object類。
所以我們把需要繼承的方法放在基類的prototype原型中,子類的實例使用基類的方法時,就會順著__proto__查找,就能拿到這個方法了。

7. 延伸 —— ES6中的class

簡單提一下class, 這是ES6中實現(xiàn)的新語法,其實就是上文所說的原型繼承的語法糖。class的使用方法:

  class Parent {
    constructor(name) {
      this.name = name
    }
    move() {
      console.log('這個方法寫在了原型中')
    }
  }
  class Son extends Parent {
    say() {
      console.log('這個方法寫在了原型中')
    }
  }
  var son = new Son('xxx')

打印son實例對象看看


可以看到,結果跟之間使用原型繼承打印出來的結果是一樣的

8. 問題 ??

截圖中 為什么實例的__proto__顯示的是 __proto__: Parent ?
如果你理解了原型繼承,我相信這個問題難不倒你


以上純屬個人理解,若有錯誤,請指正。
路漫漫其修遠兮,吾將上下而求索。共勉 ??

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容