上一章曾經介紹過,建立自定義對象的最簡單方式就是建立一個Object 的實例,而後再爲它添加屬性和方法,以下所示。
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
};瀏覽器
上面的例子建立了一個名爲person 的對象,併爲它添加了三個屬性(name、age 和job)和一個方法(sayName())。其中,sayName()方法用於顯示this.name(將被解析爲person.name)的值。
早期的JavaScript 開發人員常常使用這個模式建立新對象。幾年後,對象字面量成爲建立這種對象的首選模式。前面的例子用對象字面量語法能夠寫成這樣:函數
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};
這個例子中的person 對象與前面例子中的person 對象是同樣的,都有相同的屬性和方法。這些屬性在建立時都帶有一些特徵值(characteristic),JavaScript 經過這些特徵值來定義它們的行爲。this
屬性類型
ECMA-262 第5 版在定義只有內部才用的特性(attribute)時,描述了屬性(property)的各類特徵。ECMA-262 定義這些特性是爲了實現JavaScript 引擎用的,所以在JavaScript 中不能直接訪問它們。爲了表示特性是內部值,該規範把它們放在了兩對兒方括號中,例如[[Enumerable]]。儘管ECMA-262第3 版的定義有些不一樣,但本書只參考第5 版的描述。
ECMAScript 中有兩種屬性:數據屬性和訪問器屬性。
1. 數據屬性
數據屬性包含一個數據值的位置。在這個位置能夠讀取和寫入值。數據屬性有4 個描述其行爲的特性。
[[Configurable]]:表示可否經過delete 刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值爲true。
[[Enumerable]]:表示可否經過for-in 循環返回屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值爲true。
[[Writable]]:表示可否修改屬性的值。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值爲true。
[[Value]]:包含這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。這個特性的默認值爲undefined。
對於像前面例子中那樣直接在對象上定義的屬性,它們的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被設置爲true,而[[Value]]特性被設置爲指定的值。例如:spa
var person = {
name: "Nicholas"
};
這裏建立了一個名爲name 的屬性,爲它指定的值是"Nicholas"。也就是說,[[Value]]特性將被設置爲"Nicholas",而對這個值的任何修改都將反映在這個位置。
要修改屬性默認的特性,必須使用ECMAScript 5 的Object.defineProperty()方法。這個方法接收三個參數:屬性所在的對象、屬性的名字和一個描述符對象。其中,描述符(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"對象
這個例子建立了一個名爲name 的屬性,它的值"Nicholas"是隻讀的。這個屬性的值是不可修改的,若是嘗試爲它指定新值,則在非嚴格模式下,賦值操做將被忽略;在嚴格模式下,賦值操做將會致使拋出錯誤。
相似的規則也適用於不可配置的屬性。例如:
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
delete person.name;
alert(person.name); //"Nicholas"ip
把configurable 設置爲false,表示不能從對象中刪除屬性。若是對這個屬性調用delete,則在非嚴格模式下什麼也不會發生,而在嚴格模式下會致使錯誤。並且,一旦把屬性定義爲不可配置的,就不能再把它變回可配置了。此時,再調用Object.defineProperty()方法修改除writable 以外的特性,都會致使錯誤:
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
//拋出錯誤
Object.defineProperty(person, "name", {
configurable: true,
value: "Nicholas"
});開發
也就是說,能夠屢次調用Object.defineProperty()方法修改同一個屬性,但在把configurable特性設置爲false 以後就會有限制了。
在調用Object.defineProperty()方法時,若是不指定,configurable、enumerable 和writable 特性的默認值都是false。多數狀況下,可能都沒有必要利用Object.defineProperty()方法提供的這些高級功能。不過,理解這些概念對理解JavaScript 對象卻很是有用。get
注:IE8 是第一個實現Object.defineProperty()方法的瀏覽器版本。然而,這個版本的實現存在諸多限制:只能在DOM對象上使用這個方法,並且只能建立訪問器屬性。因爲實現不完全,建議讀者不要在IE8 中使用Object.defineProperty()方法。it
2. 訪問器屬性
訪問器屬性不包含數據值;它們包含一對兒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,而在嚴格模式下會拋出錯誤。
支持ECMAScript 5 的這個方法的瀏覽器有IE9+(IE8 只是部分實現)、Firefox 4+、Safari 5+、Opera 12+ 和Chrome 。在這個方法以前, 要建立訪問器屬性, 通常都使用兩個非標準的方法:__defineGetter__()和__defineSetter__()。這兩個方法最初是由Firefox 引入的,後來Safari 三、Chrome 1 和Opera 9.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;
alert(book.edition); //2
在不支持Object.defineProperty() 方法的瀏覽器中不能修改[[Configurable]] 和[[Enumerable]]。
定義多個屬性
因爲爲對象定義多個屬性的可能性很大,ECMAScript 5 又定義了一個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.defineProperties()方法的瀏覽器有IE9+、Firefox 4+、Safari 5+、Opera 12+和Chrome。
讀取屬性的特性
使用ECMAScript 5 的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(typeof descriptor.get); //"function"
對於數據屬性_year,value 等於最初的值,configurable 是false,而get 等於undefined。對於訪問器屬性year,value 等於undefined,enumerable 是false,而get 是一個指向getter函數的指針。在JavaScript 中,能夠針對任何對象——包括 DOM 和BOM 對象,使用Object.getOwnProperty-Descriptor()方法。支持這個方法的瀏覽器有IE9+、Firefox 4+、Safari 5+、Opera 12+和Chrome。