詳解原型鏈中的prototype和 __proto__

主帥發表於2018-11-14

詳解原型鏈中的prototype和 __proto__

前言

本文主要是徹底講明白prototype和__proto__ 是幹嘛的,相信很多同學也跟我一樣,傻傻的分不清楚兩者應該如何使用?、在原型鏈中到底起到什麼作用?、 在繼承中起到什麼作用?、javascript為什麼會設計出這兩個屬性?等一系列問題,網上查遍許多資料,看的也是雲裡霧裡一頭霧水,接下來,本文就通俗易懂的來講解以上問題,也當做自己的學習筆記。

為什麼會創造出 prototype和__proto__

javascript 沒有類的概念

如果你之前瞭解過 java 這種物件導向程式設計的語言,你應該會熟悉類的概念,在物件導向程式設計的語言中,首先設計出類(class),然後再生成類的例項(例項化成物件)。 但是 在 javascript 語言中,並沒有類的概念,所有的資料型別都是物件(object) ,這並沒有任何問題,不是所有的程式語言都必須向 java 一樣依賴類的概念,C 語言就沒有類的概念,一樣活的很好。但是我們不得不承認,類是思想會給專案工程問題帶來很多方便。如果想構建一個大型專案,那必然會涉及到繼承,繼承問題,就需要通過類來實現,但是在 javascript 中並沒有類的概念,那應該怎麼辦呢,我們可以模仿類,所以prototype和__proto__ 就衍生出來了。

也可以參考阮一峰老師的:Javascript繼承機制的設計思想

javascript 中的資料型別

在講解prototype和__proto__之前,我們首先來講解一下 javascript 中的資料型別。為什麼要先講下資料型別呢,因為prototype和__proto__用到了Function 和 Object 兩種資料型別。 javascript 資料型別分為兩種:

  • 基本資料型別:String Number Boolean Null undefined
  • 引用資料型別:Array Object Date Function RegExp 等

我們可以用 typeof 來判斷一個變數的資料型別,5種基本資料型別都可以用 typeof 判斷出來(Null為 object), 而引用資料型別只能判斷出 object 和 function 兩種型別,也就是說,引用型別其實分兩種 ObjectFunction ,其他的型別都是 Object 型別衍生出來的。

程式碼如下:

詳解原型鏈中的prototype和 __proto__

Function 和 Object

通過上面的講解,接下來我們的重點要放在 Function 和 Object 兩個型別上。

Function 和 Object 的區別
  • Function 可以被執行
  • Function 可以當做 Object 的建構函式,比如當我們使用 new操作符後面跟著一個 Function時,這個 Function會被當成建構函式返回一個物件。 程式碼如下:
    詳解原型鏈中的prototype和 __proto__

從上圖我們可以看出,建構函式本身是一個 function ,而建構函式返回的例項其實是一個object

  • 建構函式function 有 prototype 屬性,而 例項 object 沒有 prototype 屬性. 程式碼如下:
    詳解原型鏈中的prototype和 __proto__

明確了 Function 與 Object 的關係後,我們接下來講解在繼承中prototype和__proto__起到了什麼作用。

在繼承中prototype和__proto__起到了什麼作用

在類似 java 這種語言中,繼承的概念是通過類和類之間實現的,但 javascript 根本沒有類,都是物件,所以,在 javascript中,繼承的概念是通過物件和物件之間實現的。

在考慮 javascript 繼承的時候,範圍只限於引用資料型別,雖然引用資料型別分為 Function 和 Object 兩種,但在繼承問題上,不需要區分 Function 和 Object,只需要統一看成物件即可。

那麼,javascript 究竟是通過什麼來確定繼承關係的呢? 答案是 proro

__proto__和prototype 不同,prototype 只在 Function 中有,而__proto__則在Function和Object中都有。

程式碼如下:

詳解原型鏈中的prototype和 __proto__

簡單總結javascript繼承的本質: 一個物件 A的__proto__屬性指向的那個物件B,B就是 A 的原型物件(或者叫父物件),物件 A 可以使用物件 B 中的屬性和方法,同時也可以使用物件 B 的 原型物件C 上的屬性和方法,以此遞迴,就是所謂的原型鏈

示例程式碼:

詳解原型鏈中的prototype和 __proto__

以上程式碼實現了 javascript 最簡單的繼承,似乎沒什麼,一個__proto__就實現了繼承問題。那還要 prototype 做什麼呢?prototype在繼承中又起什麼作用呢,其實 prototype 真正起作用的是把 Function 當做建構函式使用的時候,因為__proto__ 並不是官方標準定義的屬性,所以藉助 prototype屬性來模仿類和類之間的繼承模式。

接下來重點分析用 Function型別構造物件過程,當你知道使用 new 操作符都做了什麼的時候,你就很清楚 prototype 的作用了。

new 操作到底做了什麼

詳解原型鏈中的prototype和 __proto__

通過上面的分析,我們知道 p 是 object 的型別,Parent 是 Function 型別。 在 java中,如果你這麼寫,那麼 Parent 應該是一個類(class),但是 javascript 中並沒有類,但是我們又很想借鑑這種語法形式,應該怎麼辦呢,這個任務只能交給 Function。 看下完整程式碼:

詳解原型鏈中的prototype和 __proto__

上面這種寫法,就是經典的構建建構函式,但是 new到底都幹了啥呢?

  • 第一步:Parent 被執行。當使用 new關鍵字時,Parent 函式會在 p 的作用域下被執行,所以這裡的 this 就是例項p,這樣,name,age,hobby三個屬性才會被當做 p 的屬性被建立,
  • 第二步:將 p.__proto__指向 Parent.prototype,這才是建構函式的精髓所在,所以,p 就繼承了 Parent.prototype中的(以及其原型鏈上的)屬性和方法。驗證如下:
    詳解原型鏈中的prototype和 __proto__

這樣,一個鮮活的例項就被建立出來了。

總結

最後,我們用Stack Overflow上關於這個問題得票最多答案作為總結,他解釋的非常簡單

下面是答案:

__proto__is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build __proto__when you create an object with new

翻譯一下:

__proto__是真正用來查詢原型鏈去獲取方法的物件。

prototype是在用new建立物件時用來構建__proto__的物件

相關文章