一、 屬性類型
ECMA-262 第 5 版在定義只有內(nèi)部才用的特性(attribute)時,描述了屬性(property)的各種特征。ECMA-262 定義這些特性是為了實現(xiàn) JavaScript 引擎用的,因此在 JavaScript 中不能直接訪問它們。為了表示特性是內(nèi)部值,該規(guī)范把它們放在了兩對兒方括號中,例如[[Enumerable]]
。
ECMAScript 中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性
1. 數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置。在這個位置可以讀取和寫入值。數(shù)據(jù)屬性有 4 個描述其行為的特性。
-
[[Configurable]]
:表示能否通過delete
刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。默認為false
-
[[Enumerable]]
: 表示能否通過for-in
循環(huán)返回屬性。默認為false
-
[[Writable]]
: 表示能否修改屬性的值。默認為false
-
[[Value]]
:包含這個屬性的數(shù)據(jù)值。 讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。這個特性的默認值為undefined
。
對于直接在對象上定義的屬性,
configurable
、enumerable
、writable
的默認值為true
要修改屬性默認的特性,必須使用 ECMAScript 5 的 Object.defineProperty()
方法。這個方法接收三個參數(shù):屬性所在的對象、屬性的名字和一個描述符對象。其中,描述符(descriptor)對象的屬性必須是: configurable
、 enumerable
、 writable
和value
。設置其中的一或多個值,可以修改對應的特性值。例如:
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
這個例子創(chuàng)建了一個名為
name
的屬性,它的值"Nicholas"
是只讀的。這個屬性的值是不可修改的,如果嘗試為它指定新值,則在非嚴格模式下,賦值操作將被忽略;在嚴格模式下,賦值操作將會導致拋出錯誤。
把 configurable
設置為false
,表示不能從對象中刪除屬性。如果對這個屬性調(diào)用 delete,則在非嚴格模式下什么也不會發(fā)生,而在嚴格模式下會導致錯誤。而且,一旦把屬性定義為不可配置的,就不能再把它變回可配置了。此時,再調(diào)用 Object.defineProperty()方法修改除 writable 之外的特性,都會導致錯誤.
2. 訪問屬性
訪問器屬性不包含數(shù)據(jù)值;它們包含一對兒 getter
和 setter
函數(shù)(不過,這兩個函數(shù)都不是必需的)。在讀取訪問器屬性時,會調(diào)用 getter
函數(shù),這個函數(shù)負責返回有效的值;在寫入訪問器屬性時,會調(diào)用setter
函數(shù)并傳入新值,這個函數(shù)負責決定如何處理數(shù)據(jù)。訪問器屬性有如下 4 個特性。
-
[[Configurable]]
:表示能否通過delete
刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。默認為false
-
[[Enumerable]]
: 表示能否通過for-in
循環(huán)返回屬性。默認為false
-
[[Get]]
:在讀取屬性時調(diào)用的函數(shù)。默認值為 undefined。 -
[[Set]]
:在寫入屬性時調(diào)用的函數(shù)。默認值為 undefined。
對于直接在對象上定義的屬性,
configurable
、enumerable
的默認值為true
訪問器屬性不能直接定義,必須使用 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
以上代碼創(chuàng)建了一個 book
對象,并給它定義兩個默認的屬性:_year
和 edition
。 _year
前面的下劃線是一種常用的記號,用于表示只能通過對象方法訪問的屬性。而訪問器屬性 year
則包含一個gette
r 函數(shù)和一個 setter
函數(shù)。 getter
函數(shù)返回_year
的值, setter
函數(shù)通過計算來確定正確的版本。因此,把 year
屬性修改為 2005
會導致_year
變成 2005
,而 edition
變?yōu)?2
。這是使用訪問器屬性的常見方式,即設置一個屬性的值會導致其他屬性發(fā)生變化。
不一定非要同時指定 getter
和 setter
。只指定 getter
意味著屬性是不能寫,嘗試寫入屬性會被忽略。在嚴格模式下,嘗試寫入只指定了 getter
函數(shù)的屬性會拋出錯誤。類似地,只指定 setter
函數(shù)的屬性也不能讀,否則在非嚴格模式下會返回 undefined
,而在嚴格模式下會拋出錯誤。
二、創(chuàng)建屬性
如果對象中不存在指定的屬性,Object.defineProperty()
就創(chuàng)建這個屬性。當描述符中省略某些字段時,這些字段將使用它們的默認值。擁有布爾值的字段的默認值都是false
。value
,get
和set
字段的默認值為undefined
。一個沒有get/set/value/writable
定義的屬性被稱為“通用的”,并被“鍵入”為一個數(shù)據(jù)描述符。
var o = {}; // 創(chuàng)建一個新對象
// 在對象中添加一個數(shù)據(jù)屬性的示例
Object.defineProperty(o, "a", {
value : 37,
writable : true,
enumerable : true,
configurable : true
});
// 對象o擁有了屬性a,值為37
// 在對象中添加一個訪問屬性的示例
var bValue;
Object.defineProperty(o, "b", {
get : function(){
return bValue;
},
set : function(newValue){
bValue = newValue;
},
enumerable : true,
configurable : true
});
o.b = 38;
// 對象o擁有了屬性b,值為38
// o.b的值現(xiàn)在總是與bValue相同,除非重新定義o.b
// 數(shù)據(jù)屬性與訪問屬性不能混合使用
Object.defineProperty(o, "conflict", {
value: 0x9f91102,
get: function() {
return 0xdeadbeef;
}
});
三、修改屬性
如果屬性已經(jīng)存在,Object.defineProperty()
將嘗試根據(jù)描述符中的值以及對象當前的配置來修改這個屬性。如果舊描述符將其configurable
屬性設置為false
,則該屬性被認為是“不可配置的”,并且沒有屬性可以被改變(除了單向改變 writable
為 false
)。當屬性不可配置時,不能在數(shù)據(jù)和訪問器屬性類型之間切換。
當試圖改變不可配置屬性(除了writable
屬性之外)的值時會拋出{jsxref("TypeError")}}
,除非當前值和新值相同。
讀取屬性的特性
使用 ECMAScript 5 的 Object.getOwnPropertyDescriptor()
方法,可以取得給定屬性的描述符。這個方法接收兩個參數(shù):屬性所在的對象和要讀取其描述符的屬性名稱。返回值是一個對象,如果是訪問器屬性,這個對象的屬性有 configurable
、 enumerable
、 get
和set
;如果是數(shù)據(jù)屬性,這個對象的屬性有 configurable
、enumerable
、 writable
和 value
。
var o, d;
o = { get foo() { return 17; } };
d = Object.getOwnPropertyDescriptor(o, "foo");
// d {
// configurable: true,
// enumerable: true,
// get: /*the getter function*/,
// set: undefined
// }
o = { bar: 42 };
d = Object.getOwnPropertyDescriptor(o, "bar");
// d {
// configurable: true,
// enumerable: true,
// value: 42,
// writable: true
// }
o = {};
Object.defineProperty(o, "baz", {
value: 8675309,
writable: false,
enumerable: false
});
d = Object.getOwnPropertyDescriptor(o, "baz");
// d {
// value: 8675309,
// writable: false,
// enumerable: false,
// configurable: false
// }