做爲一名前端工程師,必須搞懂JS中的prototype、__proto__與constructor屬性,相信不少初學者對這些屬性存在許多困惑,容易把它們混淆,本文旨在幫助你們理清它們之間的關係並完全搞懂它們。這裏說明一點,__proto__屬性的兩邊是各由兩個下劃線構成(這裏爲了方便你們看清,在兩下劃線之間加入了一個空格:_ _proto_ _),實際上,該屬性在ES標準定義中的名字應該是[[Prototype]],具體實現是由瀏覽器代理本身實現,谷歌瀏覽器的實現就是將[[Prototype]]命名爲__proto__,你們清楚這個標準定義與具體實現的區別便可(名字有所差別,功能是同樣的)。前端
console.log(Foo.__proto__ === Function.prototype);//true console.log(Object.__proto__ === Function.prototype);//true console.log(Function.prototype.constructor === Function);//true console.log(Foo.constructor === Function);//true console.log(Foo.hasOwnProperty('constructor'));//false console.log(Object.constructor === Function);//true console.log(Object.hasOwnProperty('constructor'));//false console.log(Function.__proto__ === Function.prototype);//true console.log(Function.prototype.constructor === Function);//true console.log(Function.prototype === Function.prototype);//true console.log(Function.prototype.__proto__ === Object.prototype);//true
下文既是對上面的觀點進行解說:瀏覽器
構造函數:function Foo ( ) { }; 實例對象:let f1=new Foo; let o1=new Foo;
以上代碼表示建立一個構造函數Foo(),並用new關鍵字實例化該構造函數獲得一個實例化對象f1。這裏稍微補充一下new操做符將函數做爲構造器進行調用時的過程:函數被調用,而後新建立一個對象,而且成了函數的上下文(也就是此時函數內部的this是指向該新建立的對象,這意味着咱們能夠在構造器函數內部經過this參數初始化值),最後返回該新對象的引用。雖然是簡簡單單的兩行代碼,然而它們背後的關係倒是錯綜複雜的,以下圖所示:前端工程師
看到這圖別怕,讓咱們一步步剖析,完全搞懂它們!
圖的說明:右下角爲圖例,紅色箭頭表示__proto__屬性指向、綠色箭頭表示prototype屬性的指向、棕色實線箭頭表示自己具備的constructor屬性的指向,棕色虛線箭頭表示繼承而來的constructor屬性的指向;藍色方塊表示對象,淺綠色方塊表示函數(這裏爲了更好看清,Foo()僅表明是函數,並非指執行函數Foo後獲得的結果,圖中的其餘函數同理)。圖的中間部分即爲它們之間的聯繫,圖的最左邊即爲例子代碼。
函數
首先,咱們須要牢記兩點:this
①__proto__和constructor屬性是對象所獨有的;spa
② prototype屬性是函數所獨有的。可是因爲JS中函數也是一種對象,因此函數也擁有__proto__和constructor屬性,這點是導致咱們產生困惑的很大緣由之一。上圖有點複雜,咱們把它按照屬性分別拆開,而後進行分析:
.net
第一,這裏咱們僅留下 __proto__ 屬性,它是對象所獨有的,能夠看到__proto__屬性都是由一個對象指向一個對象,即指向它們的原型對象(也能夠理解爲父對象),那麼這個屬性的做用是什麼呢?它的做用就是當訪問一個對象的屬性時,若是該對象內部不存在這個屬性,那麼就會去它的__proto__屬性所指向的那個對象(能夠理解爲父對象)裏找,若是父對象也不存在這個屬性,則繼續往父對象的__proto__屬性所指向的那個對象(能夠理解爲爺爺對象)裏找,若是還沒找到,則繼續往上找…直到原型鏈頂端null(能夠理解爲原始人。。。),再往上找就至關於在null上取值,會報錯(能夠理解爲,再往上就已經不是「人」的範疇了,找不到了,到此結束,null爲原型鏈的終點),由以上這種經過__proto__屬性來鏈接對象直到null的一條鏈即爲咱們所謂的原型鏈。prototype
第二,接下來咱們看 prototype 屬性:3d
prototype屬性,別忘了一點,就是咱們前面提到要牢記的兩點中的第二點,它是函數所獨有的,它是從一個函數指向一個對象。它的含義是函數的原型對象,也就是這個函數(其實全部函數均可以做爲構造函數)所建立的實例的原型對象,由此可知:f1.__proto__ === Foo.prototype,它們兩個徹底同樣。代理
那prototype屬性的做用又是什麼呢?
它的做用就是包含能夠由特定類型的全部實例共享的屬性和方法,也就是讓該函數所實例化的對象們均可以找到公用的屬性和方法。任何函數在建立的時候,其實會默認同時建立該函數的prototype對象。
最後,咱們來看一下 constructor 屬性:
constructor屬性也是對象才擁有的,它是從一個對象指向一個函數,含義就是指向該對象的構造函數,每一個對象都有構造函數(自己擁有或繼承而來,繼承而來的要結合__proto__屬性查看會更清楚點,以下圖所示),從上圖中能夠看出Function這個對象比較特殊,它的構造函數就是它本身(由於Function能夠當作是一個函數,也能夠是一個對象),全部函數和對象最終都是由Function構造函數得來,因此constructor屬性的終點就是Function這個函數。
感謝網友的指出,這裏解釋一下上段中「每一個對象都有構造函數」這句話。
這裏的意思是每一個對象均可以找到其對應的constructor,由於建立對象的前提是須要有constructor,而這個constructor多是對象本身自己顯式定義的或者經過__proto__在原型鏈中找到的。而單從constructor這個屬性來說,只有prototype對象纔有。每一個函數在建立的時候,JS會同時建立一個該函數對應的prototype對象,而函數建立的對象.__proto__ === 該函數.prototype,該函數.prototype.constructor===該函數自己,故經過函數建立的對象即便本身沒有constructor屬性,它也能經過__proto__找到對應的constructor,因此任何對象最終均可以找到其構造函數(null若是當成對象的話,將null除外)。以下:
總結一下:
本文就此結束了,但願對那些對JS中的prototype、__proto__與constructor屬性有困惑的同窗有所幫助。