每一個對象都有本身的屬性,這些對象的屬性描述了對象的信息和特徵。而這些對象的屬性也有本身的屬性,即對象屬性的屬性(或稱爲特徵),他們描述了對象中某個屬性的特徵,例如一個屬性是否能夠枚舉、是否能夠修改、是否能夠刪除等等。數組
注意:爲便於區分,如下部分將對象的屬性簡稱爲屬性,將對象屬性的屬性簡稱爲屬性的特徵。函數
屬性有兩種類型:ui
數據屬性:保存了值,是最經常使用的屬性類型,例以下面 person 對象的 name 屬性就是一個數據屬性:this
let person = {
name: 'Yuri'
};
複製代碼
訪問器屬性:是個函數,決定了一個屬性被讀取或寫入的方式,函數名就是要讀取或者寫入的屬性名,讀取這個屬性的函數用 get
後跟屬性名來定義,寫入一個屬性的函數用set
後跟屬性名來定義。舉個例子,假設訪問器屬性名爲 age, 則定義的語法爲:spa
/* 假設訪問器屬性名爲 age, 則定義的語法爲: */
let person = {
xxx: 99,
// 定義讀取的函數
get age(){
// do something;
return this.xxx; // xxx 將在 age 被讀取時返回
},
// 定義寫入的函數
set age(value){
this.xxx = value; // 其實是將 xxx 的屬性值設置成 value
}
}
複製代碼
這種方式從外部看來是在訪問 age
,實際上是在訪問 xxx
,實際上,經過get
和set
咱們能夠自由的處理外界的讀取與寫入請求,能夠返回一個真的值,也能夠給外部一個假的值。code
這兩類屬性定義的方式不一樣,可是訪問的方式倒是同樣的,均可以經過點號訪問或者中括號來訪問,例如:對象
// 讀取 name 值
console.log(person.name); // Yuri
// 寫入 name 值
person.name = 'new name';
console.log('修改後的 name 屬性值是 ' + person.name); // 修改後的 name 屬性值是 new name
// 讀取 age 值
console.log(person.age); // 99
// 寫入 age 值
person.age = 1;
console.log('修改後的 age 屬性值是 ' + person.age); // 修改後的 age 屬性值是 1
複製代碼
雖然兩種屬性均可以設置和讀取其中保存的值,但在做用上仍是有區別的。blog
若是隻須要保存和讀取數據,則用數據屬性來作就夠了,沒有必要使用訪問器屬性;可是若是須要修改讀取或者寫入的默認行爲時,則應該將屬性用訪問器的方式來定義成一個訪問器屬性~~,而後就能夠隨心所欲了~~。ip
在定義訪問器屬性時,不必定非要同時定義set和get
。當只定義get
時,則屬性就成了只讀屬性,不能寫入;若是隻定義了set
,則屬性就只能寫入,不能讀取。字符串
訪問器屬性的優先級較高。當將一個屬性名同時由數據屬性和訪問器屬性定義時,訪問器屬性生效,數據屬性被忽略:
let person = {
name: 'Yuri', // 將 name 設置爲數據屬性
xxx: 99,
get name(){ // 將 name 設置爲訪問器屬性
// do something;
return this.xxx;
},
// 定義寫入的函數
set name(value){ // 將 name 設置爲訪問器屬性
this.xxx = '這是經過訪問器屬性設置的值: ' + value;
}
}
// 讀取 name 值
console.log(person.name); // 99
// 寫入 name 值
person.name = 'new name';
console.log(person.name); // 這是經過訪問器屬性設置的值: new name
複製代碼
如引言中所說,對象的屬性不只有值(value),並且還有描述這個屬性的特徵,例如可讀性、可配置性,這些特徵是用一個對象來保存的,這個對象稱爲屬性的描述對象,全部的屬性都有一個描述對象來描述它的特徵。
屬性的描述對象在屬性被建立的時候就已經生成了,JavaScript會自動建立它,併爲它指定默認值。固然,咱們也能夠在建立一個屬性的時候就手動指定這個屬性的特徵,這個方法在2.2節中進行了討論。
對於上文提到的兩種屬性(數據屬性和訪問器屬性)有兩個共同的特徵,可是因爲功能和特色的不一樣,它們各自也有本身獨有的特徵,下面分別來討論。
咱們能夠手動的去修改這些特徵,例如將一個屬性的設置爲不可枚舉的,則用for in
方法或者Object.keys()
方法都不會取得這個屬性。
修改屬性的特徵的方法是Object.defineProperty(obj, propertyName, descriptor);
,其中參數定義以下:
obj
:屬性所在的對象propertyName
:屬性的名稱,是個字符串descriper
:描述屬性特徵的對象,經過修改這個對象來修改屬性的特徵上面的Object.defineProperty()
函數一次只能修改一個屬性的特徵,若是想一次性同時改變多個屬性的特徵,須要使用Object.defineProperties(obj, descriptor)
函數,其中的第一個參數也是要被修改屬性的對象,第二個參數也是一個特徵描述對象,不過在這個對象裏能夠同時修改多個屬性的特徵。
下面的內容會使用上面提到的兩個方法來改變屬性的特徵,經過閱讀下面的內容,能夠同時學會屬性每一個特徵的含義和這兩個函數的用法。
所謂通用的特徵是指數據屬性和訪問器屬性都有的特徵。這類特徵有兩個:
enumerable
:這個特徵描述了屬性是不是能夠被枚舉的,它取布爾值,若爲true
,則能夠枚舉,不然不可被枚舉。不可枚舉的屬性不能被for in
方法或者Object.keys()
方法獲得。咱們本身定義的每一個屬性的enumerable
特徵的默認值都是true
,即都是可枚舉的。
let obj = {
name: 'Yuri'
};
// 默認 name 屬性是可枚舉的, 能夠經過 Object.keys 方法獲得 name 這個屬性名
console.log(Object.keys(obj)); // ["name"]
Object.defineProperty(obj, 'name', {
enumerable: false // 設置爲不可枚舉
});
// 此時 Object.keys 獲得的結果中就沒有 name 屬性了
console.log(Object.keys(obj)); // [] 空數組
複製代碼
configurable
:描述這個屬性是否能夠被配置,取布爾值。是否能夠被配置的意思是這個屬性的descriptor
描述對象是否能被修改,同時也肯定了這個屬性是否能被刪除。咱們本身定義的每一個屬性的configurable
特徵的默認值都是true
,即都是可修改和刪除的。
首先在默認狀況下嘗試刪除一個對象的屬性:
let obj = {
name: 'Yuri'
};
// 嘗試刪除 name 這個屬性,成功
console.log(obj.name); // Yuri
delete obj.name;
console.log(obj.name); // undefined,說明 obj 對象已經沒有了 name 屬性
複製代碼
能夠看到在默認狀況下,咱們本身給對象加的屬性是能夠被刪除的,由於這個屬性的configurable
特徵取值爲true
。
下面將configurable
屬性設置爲false
,再嘗試刪除操做,發現刪除失敗:
Object.defineProperty(obj, 'name', {
configurable: false // 設置爲不可配置
});
delete obj.name;
console.log(obj.name); // Yuri,說明 obj 對象仍有 name 屬性
複製代碼
數據屬性有兩個訪問器屬性所沒有的特徵:
writable
:定義了屬性值可否被修改,去布爾值,若爲true
則可被修改,不然不能被修改。默認值爲true
。
let obj = {
name: 'Yuri'
};
console.log(obj.name); // Yuri
obj.name = 'Yuri`s Revenge';
console.log(obj.name); // Yuri`s Revenge, 修改爲功,由於 writable 默認爲 true
Object.defineProperty(obj, 'name', {
writable: false // 設置爲禁止修改
});
obj.name = 'Red alert';
console.log(obj.name); // Yuri`s Revenge, 依然是修改以前的值,修改失敗
複製代碼
value
:它保存了屬性的值,當咱們想取出屬性值時,其實取出的就是這個value
所保存的值。例如當咱們使用obj.name
來得到name
屬性的值時,獲得的就是value
的值:
let obj = {
name: 'Yuri'
};
console.log(obj.name); // Yuri
Object.defineProperty(obj, 'name', {
value: '這是經過 value 特徵修改後的值'
});
console.log(obj.name); // 這是經過 value 特徵修改後的值
複製代碼
經過給屬性的value
特徵賦值,甚至能夠給對象建立一個原本沒有的屬性並賦上值:
// 建立一個沒有 age 屬性的對象
let obj = {
name: 'Yuri'
};
console.log(obj.age); // undefined, 由於沒有 age 這個屬性,因此返回 undefined
// 使用 Object.defineProperty 給對象設置 age 屬性的值
Object.defineProperty(obj, 'age', {
value: 99
});
console.log(obj.age); // 99 已成功 age 屬性,並賦了值
複製代碼
實際上,上面代碼的執行步驟爲:先觀察對象有沒有 age 屬性,發現沒有,則建立一個,而後將value
值賦了上去。
***注意:***在用這種方法建立對象時,要記得不能只設置value
屬性,還要顯式的設置其餘三個屬性(enumerable、configurable、writable)的值,不然這三個特徵的值都會被默認設置爲false
,即不可遍歷、不可配置、不可寫。這是和其餘建立屬性的方式所不一樣的。
訪問器屬性也有兩個特有的特徵,因爲訪問器屬性不須要存儲值,因此沒有writable和value特徵,其特有的特徵是:get
和set
特徵。
這彷佛和第 1 節中討論的訪問器屬性重複了,由於訪問器屬性也是用get和set來定義的數據訪問方式。可是他們的不一樣點在於訪問器屬性只能在建立對象時定義,而使用get
和set
特徵能夠隨時改變屬性,這樣就不用再去建立一個新的對象了。
例如改變一個已經定義了訪問器屬性的get和set特徵:
let person = {
_age: 99,
get age(){
return this._age;
},
set age(value){
this._age = value;
}
};
Object.defineProperty(person, 'age', {
get(){ // 因爲已經指定了要設置的屬性名,因此沒必要向第1小節同樣 get age(){...}
return this._age * 2;
},
set(value){ // 因爲已經指定了要設置的屬性名,因此沒必要向第1小節同樣 set age(){...}
this._age = 0; // 不管外界想將 name 設置成什麼 value, 都忽略, 任性的設置成 0
}
});
console.log(person.age); // 198, 就是 99 * 2
person.age = 18;
console.log(person.age); // 0
複製代碼
注意:當僅設置了get 時,該屬性爲只讀;當僅設置了set時,該屬性爲僅可寫。
Object.defineProperties(object, descriptors)
方法的重點在於對特徵描述對象的定義,在第二個參數descriptors
中能夠給多個屬性定義各自的描述對象,屬性和描述對象是以鍵值對的形式出現的,除此以外,和Object.defineProperty
的效果沒有區別。例子以下:
let game = {
name: 'Yuri`s Revenge'
};
console.log(game.name); // Yuri`s Revenge
Object.defineProperties(game, {
name: { // 給 name 屬性修改特徵描述對象
value: 'Red Alert',
configurable: false,
writable: false
},
creater: { // 建立一個新的屬性,並只提供 get 方法,則 creater 屬性是隻讀的
get(){
return 'West Wood'
},
configurable: false,
enumerable: true
}
});
console.log(game.name); // Red Alert
game.name = 'Command & Conquer';
console.log(game.name); // Red Alert
console.log(game.creater); // West Wood
game.creater = 'EA';
console.log(game.creater); // West Wood
複製代碼
若是想知道一個屬性如今的特徵是什麼,則能夠調用Object.defineProperties(object, propertyName)
,其中第一個參數 object
是屬性所在的對象,第二個參數propertyName
是屬性名。返回結果是這個屬性的特徵描述對象。例如獲取上個例子中的game
對象的 name
和 creater
屬性的特徵描述對象:
console.log(Object.getOwnPropertyDescriptor(game, 'name'));
// {value: "Red Alert", writable: false, enumerable: true, configurable: false}
console.log(Object.getOwnPropertyDescriptor(game, 'creater'));
// {get: ƒ, set: undefined, enumerable: true, configurable: false}
// 其中 get: f 中的 f 是 function 的縮寫,表明函數
複製代碼
對象有兩種屬性:數據屬性和訪問器屬性。
對象的屬性除了有屬性值之外,也有本身的特徵,好比是否可遍歷、是否可配置等。
數據屬性和訪問器屬性都有4個特徵,除了2個共同的特徵以外,也各自擁有2個本身獨特的特徵。能夠用圖來總結一下: