前言
在segmentfault 上看到這樣一道題目:
var F = function(){};Object.prototype.a = function(){};Function.prototype.b = function(){};var f = new F();問:f 能取到a,b嗎?原理是什麼?
乍一看真的有點懵,仔細研究了一下,發現還是對原型理解不透徹,所以總結一篇,填個洞~
Function和Object
在解題之前,先再說說原型、原型鏈,以及Function 和Object 的關係,這也是本文的重點。
原型
在創建一個函數的時候,會自動為其創建一個原型對象,可以通過函數的prototype屬性訪問到。
創建一個構造函數的實例對象,該實例對象內部將包含一個指針(內部屬性),指向構造函數的原型對象。 ECMA-262 第5版中管這個指針叫[[prototype]]。雖然在腳本中沒有標準的方式訪問[[prototype]],但Firefox、 Safari、 Chrome在每個對像上都支持一個屬性__proto__,用於訪問其構造函數的原型對象。
重要的事情再說一遍:
構造函數通過prototype 屬性訪問原型對象。
實例對象通過[[prototype]] 內部屬性訪問原型對象,瀏覽器實現了_proto_ 屬性用於實例對象訪問原型對象。
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的原型對象……注意是原型對象之間的聯繫,ABC 這三個構造函數之間並沒什麼關係,所以才稱為“原型鏈”吧~
假設a是A的實例對象,則a 的原型鍊為下圖中紫色線條所示,橙色線條連接了構造函數和其原型對象。
由圖可以看出,原型鏈的末端是Object.prototype.__proto__即null。當查找a的某個屬性或方法時,首先查找a自身有沒有,沒有則沿著原型鏈一直查找,直到找到或者最後到null返回undefined。
Function 和Object
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。如果把題修改成下面的樣子, Fb()的結果是什麼呢?為什麼呢?可以想一下哦~
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__這條鏈。在不同的場景下,分清它的身份就不會錯了。
整篇下來,感覺自己說的也比較絮叨……不足之處,還請各位指正~ 至於題目,真的不知道該叫什麼好。 。
願本文能帶給堅持看完的你一些收穫~ ^_^
謝謝大家對本站的支持,後續繼續更新相關資料,幫助大家學習了解這部分知識!