雖然說標題是繼承,但繼承這塊的涉及的知識點不只僅只是繼承,因此這塊我會分紅兩個部分來說:前端
分兩節講有如下兩個緣由:面試
繼承於咱們前端來講絕對是很是熟悉也必須熟悉的一個高頻必懂知識點。熟悉到只要是面試必定會有關於繼承的問題;並且源碼中繼承的使用也隨處可見。數組
可依舊有不少前端對繼承的實現和應用沒有一個總體的把握。追其緣由無非有二:bash
- ECMAScript 繼承的實現方法區別於其餘基於類的實現繼承的面向對象(Object Oriented)語言。
- 工做中即便對如何實現繼承只知其一;不知其二,也一點都不耽誤寫邏輯代碼。
不管因爲哪個緣由,建議請儘快弄懂繼承的實現和應用,不然你可能會如同你的表情包同樣——流下了沒有技術的淚水。微信
接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文作輔助解釋。函數
在講 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__
中找。
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 的顯式原型對象中的這些方法你都知道嗎?要知道數組也是很是重要的一部分哦 ~ 咳咳咳,這是考試重點。
__proto__
[隱式原型]prototype
理解起來不難,__proto__
理解起來就會比 prototype
稍微複雜一點。不過當你理解的時候你會發現,這個過程真的頗有趣。下面咱們就講講 __proto__
。
其實這個屬性指向了 `[[prototype]]`,可是 `[[prototype]]` 是內部屬性,咱們並不能訪問到,因此使用 `__proto__` 來訪問。
我先給個有點繞的定義:
__proto__
指向了建立該對象的構造函數的顯式原型。
咱們如今仍是使用 noWork
這個例子來講。咱們發現 noWork
原型對象中還有另外一個屬性 __proto__
。
咱們先打印這個屬性:
__proto__
指向的是
Object.prototype
。
我聽到有人在問爲何?
__proto__.constructor
指向的是 Object
。__proto__.constructor
指向的是 Object.prototype.constructor
。__proto__
指向的是 Object.prototype
。如圖:
至於爲何是指向 Object? 由於全部的引用類型默認都是繼承 Object 。
做用
noWork
這個對象中的 toString()
屬性時,在noWork
中找不到,就會沿着 __proto__
依次查找。當咱們使用 new 操做符時,生成的實例對象擁有了 __proto__
屬性。即在 new 的過程當中,新對象被添加了 __proto__
而且連接到構造函數的原型上。
new 的過程
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
上。
未完待續
《前端詞典》這個系列會持續更新,每一期我都會講一個出現頻率較高的知識點。但願你們在閱讀的過程中能夠斧正文中出現不嚴謹或是錯誤的地方,本人將不勝感激;若經過本系列而有所得,本人亦將不勝欣喜。
若是你以爲個人文章寫的還不錯,能夠關注個人微信公衆號,公衆號裏會提早劇透呦。
你也能夠添加個人微信 wqhhsd, 歡迎交流。
【前端詞典】繼承(二) - 「回」的幾種寫法