在 segmentfault 上看到這樣一道題目:segmentfault
var F = function(){}; Object.prototype.a = function(){}; Function.prototype.b = function(){}; var f = new F();
問:f 能取到a,b嗎?原理是什麼?瀏覽器
乍一看真的有點懵,仔細研究了一下,發現仍是對原型理解不透徹,因此總結一篇,填個洞~函數
在解題以前,先再說說 原型、原型鏈,以及 Function 和 Object 的關係,這也是本文的重點。spa
在建立一個函數的時候,會自動爲其建立一個原型對象,能夠經過函數的prototype
屬性訪問到。prototype
建立一個構造函數的實例對象,該實例對象內部將包含一個指針(內部屬性),指向構造函數的原型對象。ECMA-262 第5版中管這個指針叫[[prototype]]
。雖然在腳本中沒有標準的方式訪問[[prototype]]
,但Firefox、 Safari、 Chrome在每一個對象上都支持一個屬性 __proto__
,用於訪問其構造函數的原型對象。指針
重要的事情再說一遍:
構造函數經過 prototype 屬性訪問原型對象。
實例對象經過 [[prototype]] 內部屬性訪問原型對象,瀏覽器實現了 _proto_ 屬性用於實例對象訪問原型對象。code
var F = function () {}; var f = new F(); // 假設F的原型對象是 p, 則 // F.prototype === p; // f.__proto__ === p;
再重複一遍。。prototype
說的是構造函數和原型對象之間的關係,__proto__
說的是實例對象和原型對象之間的關係。對象
類 A繼承B,B繼承C……其實就是A的原型對象中有指針指向B的原型對象,而B的原型對象中有指針指向C的原型對象……注意是原型對象之間的聯繫,A B C 這三個構造函數之間並沒什麼關係,因此才稱爲「原型鏈」吧~blog
假設a是A的實例對象,則 a 的原型鏈爲下圖中紫色線條所示,橙色線條鏈接了構造函數和其原型對象。繼承
由圖能夠看出,原型鏈的末端是Object.prototype.__proto__
即null
。當查找a
的某個屬性或方法時,首先查找a
自身有沒有,沒有則沿着原型鏈一直查找,直到找到或者最後到null
返回undefined
。
Function
和 Object
之間的關係有點繞:Object
是構造函數,既然是函數,那麼就是Function
的實例對象;Function
是構造函數,但Function.prototype
是對象,既然是對象,那麼就是Object
的實例對象。
一切對象都是Object
的實例,一切函數都是Function
的實例。Object
是Function
的實例,而Function.prototype
是Object
的實例。
兩者的關係以下圖所示。
可見,Object
做爲構造函數,它有 prototype
屬性指向 Object.prototype
, 做爲實例對象, 它有 Object.__proto__
指向Function.prototype
。Function
是構造函數,它有prototype
屬性指向Function.prototype
,而Function
是函數,從而也是Function
的實例,因此它有Function.__proto__
指向Function.prototype
,從而 Function.__proto__ === Function.prototype
爲 true
。
可在Chrome控制檯下進行驗證,如圖。
解決原型鏈問題最好的辦法就是畫圖了,通過前面的分析,這個圖畫起來應該不成問題,以下~
f 的原型鏈爲藍色線所畫,因此 f 能夠訪問到 a , 不能訪問到 b 。
若是不畫圖,乍一看,可能會以爲f 能夠訪問到 b,那是可能跟我同樣誤認爲F.prototype
指向Function.prototype
,但其實F.prototype
是對象而不是函數,因此它的原型對象不會是 Function.prototype
。
因此,原型鏈問題一應要畫圖啊~
在上題中,f 只能訪問 a,不能訪問 b 。但 F 既能夠訪問 a ,又能夠訪問 b。
若是把題修改爲下面的樣子, F.b()
的結果是什麼呢?爲何呢?能夠想一下哦~
var F = function(){}; Object.prototype.a = function(){}; Function.prototype.b = function(){ console.log('F.__proto__') }; F.prototype.b = function (){console.log('F.prototype');};
讀到這裏,有沒有發現函數一個比較特殊的地方?
通常的對象,只有一個__proto__
屬性用於訪問其構造函數的原型對象,而對於函數來講,它既是函數又是對象。
做爲函數,它生來就有prototype
屬性指向其原型對象函數名.prototype
。
做爲Function的實例對象,它有__proto__
屬性指向Function.prototype
一般,這兩個屬性是指向兩個對象的,但Function
的這兩個屬性指向相同,都指向Function.prototype
。
對於函數 A( )
來講,A.prototype
中的方法是供其實例對象調用的,本身並不會用;當A 做爲實例運行時,調用的是 A.__proto__
中的方法。也就是說,做爲構造函數使用時,走的是A.prototype
這條鏈,方法、屬性賦給其實例;做爲對象使用時,走的是A.__proto__
這條鏈。在不一樣的場景下,分清它的身份就不會錯了。
整篇下來,感受本身說的也比較絮叨……不足之處,還請各位指正~ 至於題目,真的不知道該叫什麼好。。
願本文能帶給堅持看完的你一些收穫~ ^_^