原文:http://www.2ality.com/2012/11/property-assignment-prototype-chain.html
本文要研究一下:一個對象的原型鏈是如何影響該對象自身的屬性賦值操作的.本文更詳細的闡述了一下上篇文章“[譯]JavaScript中的屬性:定義和賦值的區別”中提到的一個知識點.
原型鏈
每個對象都有一個包含了一個或者多個對象的原型鏈,該對象正是這個原型鏈的起始對象.原型鏈上的所有對象的所有屬性都可以被該對象訪問到.例如:
> var proto = { foo: 1 }; > var obj = { __proto__: proto, bar: 2 }; > obj.foo 1 > obj.bar 2
我們用到了特殊屬性 __proto__ [1] 來創建原型鏈(該屬性還沒有被所有瀏覽器廣泛支持).對象obj的原型鏈包含了三個對象:起始處是obj,緊跟着proto,最后是Object.prototype. Object.prototype是Object構造函數的原型對象,絕大部分原型鏈中都包含了它(大部分,但不是全部[2]):
> Object.prototype.isPrototypeOf({}) true > Object.prototype.isPrototypeOf([]) true > Object.prototype.isPrototypeOf(new Date()) true
而且它是原型鏈的截止對象:
> Object.getPrototypeOf(Object.prototype) null
普通對象的很多標准方法都是從Object.prototype上繼承下來的,比如toString()和hasOwnProperty().
為屬性賦值
如果你給一個屬性賦值,你通常只能修改原型鏈上的起始對象(也就是對象自身):如果自身屬性已經存在了,則改變這個屬性的值,否則,創建這個新的自身屬性:
> obj.foo = 3; > obj.foo 3 > obj.hasOwnProperty("foo") true > proto.foo 1
這樣設計的目的是:一個原型可以為其所有的實例引入了一個公用的初始值(被繼承的屬性的值).如果給其中一個實例的同名屬性執行賦值操作可以改變原型上的那個公用的屬性值的話,那么所有實例的初始值都會被改變.為了防止這種情況發生,同時還允許你修改某單個實例的初始值,屬性的賦值操作被設計為:僅允許你改變一個已存在的自身屬性的值.如果還沒有這個自身屬性,則會自動創建,再賦值.
訪問器和原型鏈
一個存在於原型鏈上的訪問器屬性[3]可以阻止"在該原型鏈的起始對象上創建同名的自身屬性".假如對象obj繼承了一個擁有getter和setter的對象:
var obj = { __proto__: { get foo() { return 1; }, set foo(x) { console.log("Setter called: "+x); } } };
給對象obj的屬性foo賦值的話,會調用到其原型上的setter訪問器,而不會給obj創建一個自身屬性foo,同理,讀取obj的foo屬性的話,也會調用到其原型上的getter訪問器:
> obj.foo = 2; Setter called: 2 > obj.foo 1
如果你想禁止該屬性的賦值操作的話(也就是只讀),可以不提供setter:
var obj = { __proto__: { get foo() { return 1; } } };
這樣的賦值操作,在非嚴格模式下會靜默失敗,在嚴格模式下,會拋出異常[4]:
> (function () { "use strict"; obj.foo = 2; }()); TypeError: Cannot set property foo of obj which has only a getter
原型鏈上的只讀屬性
如果原型鏈上的起始對象繼承了一個只讀屬性,則你無法通過賦值操作改變這個屬性的值.例如,下面的代碼:
var proto = Object.defineProperty({}, "foo", { value: 1, writable: false }); var obj = { __proto__: proto };
你無法給obj.foo賦值:
> (function () { "use strict"; obj.foo = 2; }()); TypeError: obj.foo is read-only
這正好和只有getter的訪問器屬性的表現相一致.這一次,原型上的屬性同樣可以作為一個共享的初始值,不同的是,我們要防止單個實例更改自己的初始值.如果你想要給obj創建一個自身屬性foo,則你可以使用Object.defineProperty()和Object.defineProperties()來完成[5].