【書名】:你不知道的JavaScript(上卷)
【作者】:Kyle Simpson
【本書總頁碼】:213
【已讀頁碼】:161
JavaScript 中的對象有一個特殊的 [[Prototype]] 內(nèi)置屬性,其實就是對于其他對象的引用。幾乎所有的對象在創(chuàng)建時 [[Prototype]] 屬性都會被賦予一個非空的值。
注意:對象的 [[Prototype]] 鏈接可以為空。

Object.create(..)會創(chuàng)建一個對象并把這個對象的 [[Prototype]] 關(guān)聯(lián)到指定的對象。
現(xiàn)在 myObject 對象的 [[Prototype]] 關(guān)聯(lián)到了 anotherObject。顯然 myObject.a 并不存在,但是盡管如此,屬性訪問仍然成功地(在 anotherObject 中)找到了值 2。
但是,如果 anotherObject 中也找不到 a 并且 [[Prototype]] 鏈不為空的話,就會繼續(xù)查找下去。
這個過程會持續(xù)到找到匹配的屬性名或者查找完整條 [[Prototype]]鏈。如果是后者的話,[[Get]] 操作的返回值是 undefined。
使用 for..in 遍歷對象時原理和查找 [[Prototype]] 鏈類似,任何可以通過原型鏈訪問到(并且是 enumerable)的屬性都會被枚舉。使用 in 操作符來檢查屬性在對象中是否存在時,同樣會查找對象的整條原型鏈(無論屬性是否可枚舉):

因此,通過各種語法進行屬性查找時都會查找 [[Prototype]] 鏈,直到找到屬性或者查找完整條原型鏈。
1.?Object.prototype
所有普通的 [[Prototype]] 鏈最終都會指向內(nèi)置的 Object.prototype。由于所有的“普通”(內(nèi)置,不是特定主機的擴展)對象都“源于”(或者說把 [[Prototype]] 鏈的頂端設(shè)置為)這個 Object.prototype 對象,所以它包含 JavaScript 中許多通用的功能。
2.?屬性設(shè)置和屏蔽
給一個對象設(shè)置屬性并不僅僅是添加一個新屬性或者修改已有的屬性值。
myObject.foo = "bar";
如果 myObject 對象中包含名為 foo 的普通數(shù)據(jù)訪問屬性,這條賦值語句只會修改已有的屬性值。
如果 foo 不是直接存在于 myObject 中,[[Prototype]] 鏈就會被遍歷,類似 [[Get]] 操作。如果原型鏈上找不到 foo,foo 就會被直接添加到 myObject 上。
如果屬性名 foo 既出現(xiàn)在 myObject 中也出現(xiàn)在 myObject 的 [[Prototype]] 鏈上層,那么就會發(fā)生屏蔽。myObject 中包含的 foo 屬性會屏蔽原型鏈上層的所有 foo 屬性,因為myObject.foo 總是會選擇原型鏈中最底層的 foo 屬性。
如果 foo 不直接存在于 myObject 中而是存在于原型鏈上層時myObject.foo = "bar" 會出現(xiàn)的三種情況。
1. 如果在[[Prototype]]鏈上層存在名為foo的普通數(shù)據(jù)訪問屬性并且沒有被標記為只讀(writable:false),那就會直接在 myObject 中添加一個名為 foo 的新屬性,它是屏蔽屬性。
2. 如果在[[Prototype]]鏈上層存在foo,但是它被標記為只讀(writable:false),那么無法修改已有屬性或者在 myObject 上創(chuàng)建屏蔽屬性。如果運行在嚴格模式下,代碼會拋出一個錯誤。否則,這條賦值語句會被忽略??傊粫l(fā)生屏蔽。
3.?如果在[[Prototype]]鏈上層存在foo并且它是一個setter,那就一定會調(diào)用這個 setter。foo 不會被添加到(或者說屏蔽于)myObject,也不會重新定義 foo 這個 setter。
如果希望在第二種和第三種情況下也屏蔽 foo,那就不能使用 = 操作符來賦值,而是使用 Object.defineProperty(..)來向 myObject 添加foo。
第二種情況可能是最令人意外的,只讀屬性會阻止 [[Prototype]] 鏈下層隱式創(chuàng)建(屏蔽)同名屬性。這樣做主要是為了模擬類屬性的繼承??梢园言玩溕蠈拥?foo 看作是父類中的屬性,它會被 myObject 繼承(復制),這樣一來 myObject 中的 foo 屬性也是只讀,所以無法創(chuàng)建。但是一定要注意,實際上并不會發(fā)生類似的繼承復制。
如果需要對屏蔽方法進行委托的話就不得不使用丑陋的顯式偽多態(tài)。
有些情況下會隱式產(chǎn)生屏蔽,一定要當心:

盡管 myObject.a++ 看起來應(yīng)該(通過委托)查找并增加anotherObject.a 屬性,但是別忘了 ++ 操作相當于 myObject.a = myObject.a + 1。因此 ++ 操作首先會通過 [[Prototype]]查找屬性 a 并從 anotherObject.a 獲取當前屬性值 2,然后給這個值加 1,接著用[[Put]]將值 3 賦給 myObject 中新建的屏蔽屬性 a。
修改委托屬性時一定要小心。如果想讓 anotherObject.a 的值增加,唯一的辦法是anotherObject.a++。