【前端詞典】繼承(一) - 原型鏈你真的懂嗎?

寫在前言前面的話

雖然說標題是繼承,但繼承這塊的涉及的知識點不只僅只是繼承,因此這塊我會分紅兩個部分來說:前端

  • 第一部分主講原型以及原型鏈
  • 第二部分主講繼承的幾種方式

分兩節講有如下兩個緣由:面試

  1. 須要瞭解繼承必須對原型和原型鏈有深入的瞭解,分開講好消化
  2. 最近孩子快出生,須要更多的時間翻閱資料來保證文章的質量

前言

繼承於咱們前端來講絕對是很是熟悉也必須熟悉的一個高頻必懂知識點。熟悉到只要是面試必定會有關於繼承的問題;並且源碼中繼承的使用也隨處可見。數組

可依舊有不少前端對繼承的實現和應用沒有一個總體的把握。追其緣由無非有二:bash

  1. ECMAScript 繼承的實現方法區別於其餘基於類的實現繼承的面向對象(Object Oriented)語言。
  2. 工做中即便對如何實現繼承只知其一;不知其二,也一點都不耽誤寫邏輯代碼。

不管因爲哪個緣由,建議請儘快弄懂繼承的實現和應用,不然你可能會如同你的表情包同樣——流下了沒有技術的淚水。微信

接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文作輔助解釋。函數

在講 ECMAScript 繼承的概念以前,我先說下原型的概念。源碼分析

類與原型

講 ECMAScript 繼承的概念以前,我先說下類的概念。(若是接觸過 Java 或者是 C++ 的話,咱們就知道 Java(C++)的繼承都是基於類的繼承)。post

類: 是面向對象(Object Oriented)語言實現信息封裝的基礎,稱爲類類型。每一個類包含數聽說明和一組操做數據或傳遞消息的函數。類的實例稱爲對象this

類: 是描述了一種代碼的組織結構形式,一種在軟件中對真實世界中問題領域的建模方法。spa

類的概念這裏我就再也不擴展,感興趣的同窗能夠自行查閱書籍。接下來咱們重點講講原型以及原型鏈

原型

JavaScript 這門語言沒有類的概念,因此 JavaScript 並不是是基於類的繼承,而是基於原型的繼承。(主要是借鑑 Self 語言原型(prototype)繼承機制)。

注意:ES6 中的 class 關鍵字和 OO 語言中的類的概念是不一樣的,下面我會講到。ES6 的 class 其內部一樣是基於原型實現的繼承。

JavaScript 摒棄轉而使用原型做爲實現繼承的基礎,是由於基於原型的繼承相比基於的繼承上在概念上更爲簡單。首先咱們明確一點,存在的目的是爲了實例化對象,而 JavaScript 能夠直接經過對象字面量語法輕鬆的建立對象。

每個函數,都有一個 prototype 屬性。 全部經過函數 new 出來的對象,這個對象都有一個 __proto__ 指向這個函數的 prototype。 當你想要使用一個對象(或者一個數組)的某個功能時:若是該對象自己具備這個功能,則直接使用;若是該對象自己沒有這個功能,則去 __proto__ 中找。

1. prototype [顯式原型]

prototype 是一個顯式的原型屬性,只有函數才擁有該屬性。
每個函數在建立以後都會擁有一個名爲 prototype 的屬性,這個屬性指向函數的原型對象。( 經過 Function.prototype.bind 方法構造出來的函數是個例外,它沒有 prototype 屬性 )

prototype 是一個指針,指向的是一個對象。好比 Array.prototype 指向的就是 Array 這個函數的原型對象。

在控制檯中打印 console.log(Array.prototype) 裏面有不少方法。這些方法都以事先內置在 JavaScript 中,直接調用便可。上面我標紅了兩個特別的屬性 constructor__proto__。這兩個屬性接下來我都會講。

咱們如今寫一個 function noWork(){} 函數。

當我寫了一個 noWork 這個方法的時候,它自動建立了一個 prototype 指針屬性(指向原型對象)。而這個被指向的原型對象自動得到了一個 constructor (構造函數)。細心的同窗必定發現了: constructor 指向的是 noWork

noWork.prototype.constructor === noWork     // true
複製代碼

一個函數的原型對象的構造函數是這個函數自己

如圖:

tips: 圖中打印的 Array 的顯式原型對象中的這些方法你都知道嗎?要知道數組也是很是重要的一部分哦 ~ 咳咳咳,這是考試重點。

2. __proto__[隱式原型]

prototype 理解起來不難,__proto__ 理解起來就會比 prototype 稍微複雜一點。不過當你理解的時候你會發現,這個過程真的頗有趣。下面咱們就講講 __proto__

其實這個屬性指向了 `[[prototype]]`,可是 `[[prototype]]` 是內部屬性,咱們並不能訪問到,因此使用 `__proto__` 來訪問。

我先給個有點繞的定義:

__proto__ 指向了建立該對象的構造函數的顯式原型。

咱們如今仍是使用 noWork 這個例子來講。咱們發現 noWork 原型對象中還有另外一個屬性 __proto__

咱們先打印這個屬性:

咱們發現這個 __proto__ 指向的是 Object.prototype

我聽到有人在問爲何?

  1. 由於這個 __proto__.constructor 指向的是 Object
  2. 咱們知道:一個函數的原型對象的構造函數是這個函數自己
  3. 因此這個 __proto__.constructor 指向的是 Object.prototype.constructor
  4. 進而 __proto__ 指向的是 Object.prototype

如圖:

咱們來驗證一下:

至於爲何是指向 Object? 由於全部的引用類型默認都是繼承 Object 。

做用

  1. 顯式原型:用來實現基於原型的繼承與屬性的共享。
  2. 隱式原型:構成原型鏈,一樣用於實現基於原型的繼承。 舉個例子,當咱們使用 noWork 這個對象中的 toString() 屬性時,在noWork 中找不到,就會沿着 __proto__ 依次查找。

3. new 操做符

當咱們使用 new 操做符時,生成的實例對象擁有了 __proto__屬性。即在 new 的過程當中,新對象被添加了 __proto__ 而且連接到構造函數的原型上。

new 的過程

  1. 新生成了一個對象
  2. 連接到原型
  3. 綁定 this
  4. 返回新對象

Function.__proto__ === Function.prototype

難道這表明着 Function 本身產生了本身? 要說明這個問題咱們先從 Object 提及。

咱們知道全部對象均可以經過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個對象,可是這個對象卻不是 Object 創造的,而是引擎本身建立了 Object.prototype 因此能夠這樣說:

全部實例都是對象,可是對象不必定都是實例。

接下來咱們來看 Function.prototype 這個特殊的對象:

打印這個對象,會發現這個對象實際上是一個函數。咱們知道函數都是經過 new Function() 生成的,難道 Function.prototype 也是經過 new Function() 產生的嗎?這個函數也是引擎本身建立的。

首先引擎建立了 Object.prototype ,而後建立了 Function.prototype ,而且經過 __proto__ 將二者聯繫了起來。

這就是爲何 Function.prototype.bind() 沒有 prototype 屬性。由於 Function.prototype 是引擎建立出來的對象,引擎認爲不須要給這個對象添加 prototype 屬性。

對於爲何 Function.__proto__ 會等於 Function.prototype ? 我看到的一個解釋是這樣的:
其餘全部的構造函數均可以經過原型鏈找到 Function.prototype ,而且 function Function() 本質也是一個函數,爲了避免產生混亂就將 function Function()__proto__ 聯繫到了 Function.prototype 上。

繼承的幾種方式

未完待續

參考

  1. 《JavaScript 高級程序設計》
  2. 《你不知道的 JavaScript - 上》
  3. 《JavaScript 語言精粹》
  4. ~zepto設計和源碼分析

前端詞典系列

《前端詞典》這個系列會持續更新,每一期我都會講一個出現頻率較高的知識點。但願你們在閱讀的過程中能夠斧正文中出現不嚴謹或是錯誤的地方,本人將不勝感激;若經過本系列而有所得,本人亦將不勝欣喜。

若是你以爲個人文章寫的還不錯,能夠關注個人微信公衆號,公衆號裏會提早劇透呦。

你也能夠添加個人微信 wqhhsd, 歡迎交流。

下期預告

【前端詞典】繼承(二) - 「回」的幾種寫法

傳送門

  1. 【前端詞典】和媳婦講代理後的意外收穫
  2. 【前端詞典】滾動穿透問題的解決方案
  3. 繼承(一) - 原型鏈你真的懂嗎?
相關文章
相關標籤/搜索