ECMA-262第5版在定義只有內部才用的特性(attribute)時,描述了屬性(property)的各類特徵。ECMA-262定義這些特性是爲了實現JavaScript引擎用的,所以在JavaScript中不能直接訪問它們。爲了表示特性是內部值,該規範把它們放在兩對兒方括號中,例如[[Enumerable]]
。編程
ECMAScript中有兩種屬性:數據屬性和訪問器屬性數組
數據屬性包含一個數據值的位置。在這個位置能夠讀取和寫入值。數據屬性有4個描述其行爲的特性。瀏覽器
[[Configurable]]
delete
刪除屬性從而從新定義屬性(可配置的)true
。[[Enumerable]]
for-in
循環遍歷屬性(可枚舉的)true
[[Writable]]
true
[[Value]]
undefined
對於直接在對象上定義的屬性,它們的[[Configurable]]
、[[Enumerable]]
、[[Writable]]
特性都被設置爲true,而[[Value]]
特性被設置爲指定的值。例如:閉包
var person = { name:"Nicholas" };
這裏建立了一個名爲name的屬性,爲它指定的值是"Nicholas"。也就是說,[[Value]]
特性將被設置爲"Nicholas",而對這個值的任何修改都將反映在這個位置。函數
要修改屬性默認的特性,必須使用ECMAScript5的Object.defineProperty()
方法。這個方法接受三個參數:屬性所在的對象、屬性的名字和一個描述符對象。其中,描述符(descriptor)對象的屬性必須是:confirgurable
、enumerable
、writable
和value
。設置其中的一或多個值,能夠修改對應的特性值。例如:oop
var person = {}; Object.defineProperty(person,"name",{ writable:false, value:"Nicholas" }); console.log(person.name); // "Nicholas" person.name = "Greg"; console.log(person.name); // "Nicholas"
建立了一個名爲name的屬性,它的值"Nicholas"是隻讀的。這個屬性的值是不可修改的,若是嘗試爲它指定新值,則在非嚴格模式下,賦值操做將被忽略;在嚴格模式下,賦值操做會致使拋出錯誤。this
相似的規則也適用與不可配製的屬性。例如:spa
var person = {}; Object.defineProperty(person,"name",{ configurable:false, value:"Nicholas" }); console.log(person.name); // "Nicholas" delete person.name; console.log(person.name); // "Nicholas"
把configurable
設置爲false
,表示不能從對象中刪除屬性。若是對這個屬性調用delete
,則在非嚴格模式下什麼也不會發生,而在嚴格模式下會致使錯誤。
並且,一旦把屬性定義爲不可配置的(false
),就不能再把它變回可配置了。此時,再調用Object.defineProperty()方法修改除writable
以外的特性,都會致使錯誤:prototype
var person = {}; Object.defineProperty(person,"name",{ configurable:false, value:"Nicholas" }); // 拋出錯誤 "Uncaught TypeError: Cannot redefine property: name" Object.defineProperty(person,"name",{ configurable:true, value:"Nicholas" });
Object.defineProperty()
方法修改同一個屬性,但若是把configurable
特性設置爲false
以後,就會有限制了。configurable
、enumerable
和writable
特性的默認值都是false
。多數狀況下,可能都沒有必要利用Object.defineProperty()
提供的這些高級功能。不過,理解這些概念對理解JavaScript對象卻很是有用。設計
IE8是第一個實現Object.defineProperty()
方法的瀏覽器版本。然而,這個版本的實現存在諸多限制:只能在DOM對象上使用這個方法,並且只能建立訪問器屬性。因爲實現不完全,建議不要在IE8中使用Object.defineProperty()
方法。
訪問器屬性不包含數據值;它們包含一對兒getter
和setter
函數(不過,這兩個函數都不是必需的)。
getter
函數,這個函數負責返回有效的值;setter
函數並傳入新值,這個函數負責決定如何處理數據。訪問器屬性有4個特性:
[[Configurable]]
delete
刪除屬性從而從新定義屬性true
[[Enumerable]]
for-in
循環遍歷屬性true
。[[Get]]
undefined
[[Set]]
undefined
訪問器屬性不能直接定義,必須使用Object.defineProperty()
來定義。
var book = { _year:2004, edition:1 }; Object.defineProperty(book,"year",{ get:function () { return this._year; }, set:function (newValue) { if ((newValue > 2004)) { this._year = newValue; this.edition += newValue - 2004; } } }) book.year = 2005; alert(book.edition); // 2
建立一個book對象,並給它定義兩個默認的屬性:_year
、edition
。_year
前面的下劃線是一種經常使用的記號,用於表示只能經過對象方法訪問的屬性。而訪問器屬性year
則包含一個getter
函數和一個setter
函數。
getter
函數返回_year
的值setter
函數經過計算來肯定正確的版本所以,把year
屬性修改成2005會致使_year
變成2005,而edition
變爲2。這是使用訪問器屬性的常見方式,即設置一個屬性的值會致使其餘屬性發生變化。
不必定非要同時指定getter和setter。只指定getter意味着屬性是不能寫,嘗試寫入屬性會被忽略。在嚴格模式下,嘗試寫入只指定了getter函數的的屬性會拋出錯誤。相似地,沒有指定setter函數的屬性也不能讀,不然在非嚴格模式下會返回undefined,在嚴格模式下會拋出錯誤。
// 下劃線表示只能經過對象方法訪問的屬性 var obj = { _x:"obj._x", _y:"obj._y", _z:"obj._z" }; // Object.defineProperty(obj,"x",{ // x屬性,只讀不能寫 get:function () { return this._x; } }); console.log(obj.x); // "obj._x" 能夠讀取,調用obj.x實際上調用了obj._x的getter函數 obj.x = "Rewrite x"; // 嘗試修改x屬性 console.log(obj.x); // "obj._x" 寫入失敗 // Object.defineProperty(obj,"y",{ // y屬性,只寫不能讀 set:function (newValue) { this._y = newValue; } }); console.log(obj.y); // "undefined" 讀取失敗 obj.y = "Rewrite obj.y"; // 嘗試修改屬性 console.log(obj._y); // "Rewrite obj.y" 能夠寫入 // Object.defineProperty(obj,"z",{ // z屬性可讀可寫 get:function () { return this._z; }, set:function (newValue) { this._z = newValue; } }); console.log(obj.z); // "obj._z" obj.z = "Rewrite obj.z"; // 修改z屬性 console.log(obj._z); // "Rewrite obj._z"
支持ES5這個方法的瀏覽器有IE9+(IE8部分實現)、Firefox4+、Safari5+、Opera12+和Chrome。在這個方法以前,建立訪問器屬性通常使用兩個標準方法:_defineGetter_()和_defineSetter_()。這兩個方法最初是由Firefox引入的,後來Safari三、Chrome1He Opera9.5也給出了相同的實現。使用這兩個遺留的方法,能夠像下面這樣重寫前面的例子。
var book = { _year:2004, edition:1 }; // 定義訪問器的舊有方法 book.__defineGetter__("year",function(){ return this._year; }); book.__defineSetter__("year",function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } }); book.year = 2005; console.log(book.edition); // 2
在不支持Object.defineProperty()方法的瀏覽器中不能修改[[Configurable]]
和[[Enumerable]]
。
Object.defineProperties()
方法。var book = {}; Object.defineProperties(book,{ _year:{ value:2004 }, edition:{ value:1 }, year:{ get:function () { return this._year; }, set:function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }, });
以上代碼在book對外上定義了兩個數據屬性(_year
和edition
)和一個訪問器屬性(year
)。最終的對象與上一節中定義的對象相同。惟一區別是這裏的屬性都是在同一時間建立的。
Object.getOwnPropertyDescriptor()
使用ES5的Object.getOwnPropertyDescriptor()
方法,能夠取得給定屬性的描述符,這個方法接收兩個參數:屬性所在的對象和要讀取其描述符的屬性名稱。返回值是一個對象,
若是是訪問器屬性,這個對象的屬性有configurable
、enumerable
、get
和set
;
若是是數據屬性,這個對象的屬性有configurable
、enumerable
、writable
、value
。
var book = {}; Object.defineProperties(book,{ _year:{ value:2004 }, edition:{ value:1 }, year:{ get:function () { return this._year; }, set:function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } }); var descriptor = Object.getOwnPropertyDescriptor(book,"_year"); alert(descriptor.value); // 2004 alert(descriptor.configurable); // false alert(typeof descriptor.get); // "undefined" var descriptor = Object.getOwnPropertyDescriptor(book,"year"); alert(descriptor.value); // "undefined" alert(descriptor.enumerable); // false alert(descriptor.get); // "function"
對於數據屬性_year,value等於最初的值,configurable是false,而get等與undefined。
對於訪問器屬性year,value等於undefined,enumerable是false,而get是一個指向getter函數的指針。
DOM
(Document Model Object)和BOM
(Brower Model Object)對象。Objecct.create
方法的第二個參數是一個關聯數組,其鍵爲屬性名,其值爲屬性描述符(屬性對象)。
下面是個具體例子。其中屬性值是經過value屬性指定的。大部分屬性默認值是false,這個例子中會將它們顯式地指定爲true。
var obj = {x:2,y:3}; // 與下面代碼等價 var obj = Object.create(Object.prototype,{ x:{ value:2, writable:true, enumerable:true, configurable:true }, y:{ value:3, writable:true, enumerable:true, configurable:true } });
能夠經過Object.create
來顯式地指定屬性的屬性。
只要將get
屬性與set
屬性指定爲相應的函數,就可以定義一個只可以經過訪問器getter
和setter
來訪問值的屬性。訪問器與value
屬性是互相排斥的,也就是說,若是指定了value
屬性的值,訪問器(同時包括get
和set
)就會失效;反之,若是指定了訪問器(get
或set
中的某一個),value
屬性就會失效。
get
、set
)與value
互斥從內部來看,將屬性做爲右值訪問時使用的是getter
函數,而將屬性做爲左值進行賦值時使用的是setter
函數。
只要能寫出正確的getter
訪問器函數,就能依以此爲基礎設計出一個不可變對象。
var obj = Object.create(Object.prototype,{ x:{ get:function () { alert("get called"); }, set:function () { alert("set called"); } } }); alert(obj.x); // "get called" 當要讀取屬性時,將會調用getter函數 // undefined 因爲getter函數沒有返回值,默認返回undefined obj.x = 1; // "set called" 當要寫入屬性值時,將調用setter函數 alert(obj.x); //"get called" 驗證了setter函數並無修改屬性,依舊是原屬性 // undefined 調用getter函數,沒有指定返回值,默認返回undefined
訪問器函數也能夠經過對象字面量來表述。下面代碼中的對象與上圖是同樣的。
var obj = { get x() { alert("get called"); }, set x(v) { alert("set called"); } };
getter函數與setter函數中的this引用指向的是當前對象,不過下面這樣的代碼是沒法運行的。這是由於其中的每一個訪問器函數都會不斷調用訪問器函數。(?!)
// 沒法運行,有無限loop的致命錯誤 var obj = Object.create(Object.prototype,{ x:{ get:function () {return this.x}, set:function (v) {this.x = v} } }); alert(obj.x); // 讀取obj.x,隱式調用了x屬性的getter函數,函數的this引用指向obj,則this.x等價於調用obj.x。出現循環錯誤
下面的代碼雖然可以運行,但仍是有些問題
// 使用了隱藏的屬性_x的訪問器的例子(姑且算是可以運行),x做爲訪問器屬性 var obj = Object.create(Object.prototype,{ x:{ get:function () {return this._x}, set:function (v) {return this._x = v}, x:{writable:true} } }); obj._x = "obj._x"; alert(obj.x); // obj.x obj.x = "Rewrite obj._x"; alert(obj.x); // "Rewrite obj._x"
這段代碼的問題指出在於屬性_x
是能夠從外部改寫的(不符合訪問器屬性規範)
不借助與規範的更恰當的方法是經過閉包來隱藏這個變量。
function createObject() { var _x = 0; // 變量名也能夠用x,不過容易產生混亂,因此這裏仍使用_x // 返回了一個定義了訪問器的對象 return { get x() {return _x}, set x(v) {_x = v} }; }; var obj = createObject(); // 生成對象 alert(obj.x); // 讀取(在內部調用getter) // 0 obj.x = 1; // 改寫(在內部調用setter) alert(obj.x); // 1