繼承是咱們前端必須熟悉的一個知識點。可依舊有不少前端對繼承的實現和應用沒有一個總體的把握。追其緣由無非有二:前端
ECMAScript 繼承的實現方法區別於其餘基於類的實現繼承的面向對象(Object Oriented)語言。數組
工做中即便對如何實現繼承只知其一;不知其二,也一點都不耽誤寫邏輯代碼。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__
中找。
prototype
[顯式原型]
prototype
是一個顯式的原型屬性,只有函數才擁有該屬性。
每個函數在建立以後都會擁有一個名爲prototype
的屬性,這個屬性指向函數的原型對象。( 經過Function.prototype.bind
方法構造出來的函數是個例外,它沒有prototype
屬性 )。
prototype
是一個指針,指向的是一個對象。好比 Array.prototype
指向的就是 Array 這個函數的原型對象。
在控制檯中打印 console.log(Array.prototype)
裏面有不少方法。這些方法都以事先內置在 JavaScript 中,直接調用便可。上面我標紅了兩個特別的屬性 constructor
和 __proto__
。這兩個屬性接下來我都會講。
咱們如今寫一個 functionnoWork(){}
函數。
當我寫了一個 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 的過程
新生成了一個對象
連接到原型
綁定 this
返回新對象
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
上。
若是你想進【大前端交流羣】,關注公衆號點擊「交流加羣」添加機器人自動拉你入羣。關注我第一時間接收最新干貨。