js中的prototype、__proto__與constructor

1. 前言

  作爲一名前端工程師,必須搞懂JS中的prototype__proto__constructor屬性,相信很多初學者對這些屬性存在許多困惑,容易把它們混淆,本文旨在幫助大家理清它們之間的關係並徹底搞懂它們。這裏說明一點,__proto__屬性的兩邊是各由兩個下劃線構成(這裏爲了方便大家看清,在兩下劃線之間加入了一個空格:_ _proto_ _,讀作“dunder proto”,“double underscore proto”的縮寫),實際上,該屬性在ES標準定義中的名字應該是[[Prototype]],具體實現是由瀏覽器代理自己實現,谷歌瀏覽器的實現就是將[[Prototype]]命名爲__proto__,大家清楚這個標準定義與具體實現的區別即可(名字有所差異,功能是一樣的),可以通過該方式檢測引擎是否支持這個屬性:Object.getPrototypeOf({__proto__: null}) === null。本文基於谷歌瀏覽器(版本 72.0.3626.121)的實驗結果所得。
   現在正式開始! 讓我們從如下一個簡單的例子展開討論,並配以相關的圖幫助理解:

function Foo() {...};
let f1 = new Foo();

上代碼表示創建一個構造函數Foo(),並用new關鍵字實例化該構造函數得到一個實例化對象f1。這裏稍微補充一下new操作符將函數作爲構造器進行調用時的過程:函數被調用,然後新創建一個對象,並且成了函數的上下文(也就是此時函數內部的this是指向該新創建的對象,這意味着我們可以在構造器函數內部通過this參數初始化值),最後返回該新對象的引用,詳細請看:詳解JavaScript中的new操作符。雖然是簡簡單單的兩行代碼,然而它們背後的關係卻是錯綜複雜的,如下圖所示:
整體的聯繫看到這圖別怕,讓我們一步步剖析,徹底搞懂它們!
  圖的說明:右下角爲圖例,紅色箭頭表示__proto__屬性指向、綠色箭頭表示prototype屬性的指向、棕色實線箭頭表示本身具有的constructor屬性的指向,棕色虛線箭頭表示繼承而來的constructor屬性的指向;藍色方塊表示對象,淺綠色方塊表示函數(這裏爲了更好看清,Foo()僅代表是函數,並不是指執行函數Foo後得到的結果,圖中的其他函數同理)。圖的中間部分即爲它們之間的聯繫,圖的最左邊即爲例子代碼。

2. _ _ proto _ _ 屬性

  首先,我們需要牢記兩點:①__proto__constructor屬性是對象獨有的;② prototype屬性是函數所獨有的。但是由於JS中函數也是一種對象,所以函數也擁有__proto__constructor屬性,這點是致使我們產生困惑的很大原因之一。上圖有點複雜,我們把它按照屬性分別拆開,然後進行分析:
__proto__
  第一,這裏我們僅留下 __proto__ 屬性,它是對象所獨有的,可以看到__proto__屬性都是由一個對象指向一個對象,即指向它們的原型對象(也可以理解爲父對象),那麼這個屬性的作用是什麼呢?它的作用就是當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那麼就會去它的__proto__屬性所指向的那個對象(可以理解爲父對象)裏找,如果父對象也不存在這個屬性,則繼續往父對象的__proto__屬性所指向的那個對象(可以理解爲爺爺對象)裏找,如果還沒找到,則繼續往上找…直到原型鏈頂端null(可以理解爲原始人。。。),再往上找就相當於在null上取值,會報錯(可以理解爲,再往上就已經不是“人”的範疇了,找不到了,到此結束,null爲原型鏈的終點),由以上這種通過__proto__屬性來連接對象直到null的一條鏈即爲我們所謂的原型鏈
  其實我們平時調用的字符串方法、數組方法、對象方法、函數方法等都是靠__proto__繼承而來的。

3. prototype屬性

  第二,接下來我們看 prototype 屬性:
prototype屬性  prototype屬性,別忘了一點,就是我們前面提到要牢記的兩點中的第二點,它是函數所獨有的,它是從一個函數指向一個對象。它的含義是函數的原型對象,也就是這個函數(其實所有函數都可以作爲構造函數)所創建的實例的原型對象,由此可知:f1.__proto__ === Foo.prototype,它們兩個完全一樣。那prototype屬性的作用又是什麼呢?它的作用就是包含可以由特定類型的所有實例共享的屬性和方法,也就是讓該函數所實例化的對象們都可以找到公用的屬性和方法。任何函數在創建的時候,其實會默認同時創建該函數的prototype對象。

4. constructor屬性

  最後,我們來看一下 constructor 屬性:
constructor屬性  constructor屬性也是對象才擁有的,它是從一個對象指向一個函數,含義就是指向該對象的構造函數,每個對象都有構造函數(本身擁有或繼承而來,繼承而來的要結合__proto__屬性查看會更清楚點,如下圖所示),從上圖中可以看出Function這個對象比較特殊,它的構造函數就是它自己(因爲Function可以看成是一個函數,也可以是一個對象),所有函數和對象最終都是由Function構造函數得來,所以constructor屬性的終點就是Function這個函數。
constructor繼承
  感謝網友的指出,這裏解釋一下上段中“每個對象都有構造函數”這句話。這裏的意思是每個對象都可以找到其對應的constructor,因爲創建對象的前提是需要有constructor,而這個constructor可能是對象自己本身顯式定義的或者通過__proto__在原型鏈中找到的。而單從constructor這個屬性來講,只有prototype對象纔有。每個函數在創建的時候,JS會同時創建一個該函數對應的prototype對象,而函數創建的對象.__proto__ === 該函數.prototype,該函數.prototype.constructor===該函數本身,故通過函數創建的對象即使自己沒有constructor屬性,它也能通過__proto__找到對應的constructor,所以任何對象最終都可以找到其構造函數(null如果當成對象的話,將null除外)。如下:
constructor說明

5. 總結

   總結一下:

  1. 我們需要牢記兩點:①__proto__constructor屬性是對象所獨有的;② prototype屬性是函數所獨有的,因爲函數也是一種對象,所以函數也擁有__proto__constructor屬性。
  2. __proto__屬性的作用就是當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那麼就會去它的__proto__屬性所指向的那個對象(父對象)裏找,一直找,直到__proto__屬性的終點null,再往上找就相當於在null上取值,會報錯。通過__proto__屬性將對象連接起來的這條鏈路即我們所謂的原型鏈
  3. prototype屬性的作用就是讓該函數所實例化的對象們都可以找到公用的屬性和方法,即f1.__proto__ === Foo.prototype
  4. constructor屬性的含義就是指向該對象的構造函數,所有函數(此時看成對象了)最終的構造函數都指向Function
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章