一篇文章讓你搞懂原型和原型鏈

葡萄城技術團隊發表於2020-11-20

本文由葡萄城技術團隊原創並首發

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

 

與多數物件導向的開發語言有所不同,雖然JavaScript沒有引入類似類的概念(ES6已經引入了class語法糖),但它仍然能夠大量的使用物件,那麼如何將所有物件聯絡起來就成了問題。於是就有了本文中我們要講到的原型和原型鏈的概念。

原型和原型鏈作為深入學習JavaScript最重要的概念之一,如果掌握它了後,弄清楚例如:JavaScript的繼承,new關鍵字的原來、封裝及優化等概念將變得不在話下,那麼下面我們開始關於原型和原型鏈的介紹。

什麼是原型?

JS中的物件包含了一個prototype的內部屬性,這個屬性所對應的就是該物件的原型。

我們先看下圖:a、b、c 分別為陣列、物件、函式。

 可以看到,三者都有一個屬性:__proto__

 

 

這個 __proto__ 稱作 隱式原型。

除此之外,c還有一個屬性:prototype

 

這個prototype 稱作 顯式原型。

小結一下:

  • 所有引用型別(函式,陣列,物件)都擁有__proto__屬性(隱式原型)
  • 所有函式除了有_proto_屬性之外還擁有prototype屬性(顯式原型)
  • 原型物件:每建立一個函式,該函式會自動帶有一個prototype屬性,該屬性是一個指標,指向了一個物件,我們稱之為原型物件。

函式除了有_proto_屬性之外還擁有prototype屬性,我們藉助建構函式來尋找二者之間的關係。如下圖:

 

 

 

總結如下(結合上圖更容易理解):

1. 例項物件a只有__proto__(隱式原型),建構函式既有 __proto__(隱式原型)也有prototype(顯式原型)

2. __proto__ 和 prototype 都是一個物件,既然是物件,就表示他們也有一個 __proto__

a.__proto__.__proto__ 
A.prototype.__proto__

3.例項物件a的隱式原型指向它建構函式的顯式原型,指向的意思是恆等於  

a.__proto__ === A.prototype

4. 當呼叫某種方法或查詢某種屬性時,首先會在自身呼叫和查詢,如果自身並沒有該屬性或方法,則會去它的__proto__屬性中呼叫查詢,也就是它建構函式的prototype中呼叫查詢。

什麼是原型鏈?

先看下圖,提出一個問題:

1. 在 Object的顯式原型新增屬性b,為什麼 示例物件p 能使用此屬性呢? p.b = b

2. 為什麼 p.a 為undefined

 

 

如下圖所示

1.    例項物件p的隱式原型(__proto__)是一個物件,有兩個屬性值:constructor 和 __proto__

2.   p.__proto__.constructor 返回的結果為建構函式Person

3.   p.__proto__.__proto__ .constructor 返回的結果為Object()函式

 

 

結合上面所講的顯式原型與隱式原型之間的關係,等同如下:

1.  p.__proto__.__proto__  =  Object.prototype

所以p.b列印結果為b,p沒有b屬性,會一直通過__proto__向上查詢,最後當查詢到Object.prototype時找到,最後列印出b,向上查詢過程中,得到的是Object.prototype,而不是Function.prototype,找不到a屬性,所以結果為undefined,這就是 原型鏈,通過__proto__向上進行查詢,最終到null結束。

總結:

1.   查詢屬性,如果本身沒有,則會去__proto__中查詢,也就是建構函式的顯式原型中查詢,如果建構函式的顯式原型中也沒有該屬性,因為建構函式的顯式原型也是物件,也有__proto__,那麼會去它的顯式原型中查詢,一直到null,如果沒有則返回undefined

2.   p.__proto__.constructor  == function Person(){}

3.   p.___proto__.__proto__== Object.prototype

4.   p.___proto__.__proto__.__proto__== Object.prototype.__proto__ == null        

5.    通過__proto__形成原型鏈而非protrotype

 

什麼是原型繼承?

Person.prototype 只是一個指標,指向的是原型物件,但是這個原型物件並不特別,它也只是一個普通物件。假設說,這時候,我們讓 Person.prototype 不再指向最初的原型物件,而是另一個類 (Animal)的例項,情況會怎樣呢?

 

 

執行該程式碼   Person.prototype = new Animal() 後,Person的prototype指標指向發生了變化,指向了一個 Animal 例項。

 

當 p 去訪問 address 屬性時,js會先在 p 的例項屬性中查詢,發現找不到後,就會去 Person 的原型物件上 查詢。因為Person的原型物件已經被我們換成一個animal例項,所以就會先找animal例項的屬性,當發現還是沒有 address屬性,就會去Animal的原型物件上查詢,最終找到。

這就說明,我們可以通過原型鏈的方式,實現 Person 繼承 Animal 的所有屬性和方法。

結語

看到這,相信大家對原型和原型鏈的概念應該已經有了一定了解了,如果仍然不太理解,也不用氣餒,因為閉包及原型鏈是JavaScript最難理解的幾部分。相信之後在不斷的開發實踐中會使你理解的更為透徹,多學習多思考才能更快掌握。如果大家有任何反饋和問題,也歡迎通過評論區告訴我,謝謝。

 

相關文章