【前端詞典】必備知識-原型與原型鏈

前言

繼承是咱們前端必須熟悉的一個知識點。可依舊有不少前端對繼承的實現和應用沒有一個總體的把握。追其緣由無非有二:前端

  1. ECMAScript 繼承的實現方法區別於其餘基於類的實現繼承的面向對象(Object Oriented)語言。數組


  2. 工做中即便對如何實現繼承只知其一;不知其二,也一點都不耽誤寫邏輯代碼。app

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

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

在講 ECMAScript 繼承的概念以前,我先說下原型的概念。this

類與原型

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

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

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

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

原型

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 這個函數的原型對象。

image.png

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

咱們如今寫一個 functionnoWork(){} 函數。image.png

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



  1. noWork.prototype.constructor === noWork     // true


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

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


2. __proto__[隱式原型]

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

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

我先給個有點繞的定義:

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

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

咱們先打印這個屬性:image.png咱們發現這個 __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 這個特殊的對象:

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

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

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

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

後話

若是你想進【大前端交流羣】,關注公衆號點擊「交流加羣」添加機器人自動拉你入羣。關注我第一時間接收最新干貨。

image.png

相關文章
相關標籤/搜索