JavaScript 核心觀念(54)- 物件屬性延伸章節:屬性的特徵 - 屬性特徵是什麼?

前言

接下來將會額外補充關於物件的屬性特徵,而這邊的觀念理解後,是可以大大增加自己對於框架的理解有幫助。

屬性特徵

其實一般來說,你在 Chrome 輸入以下程式碼,就只會看到單純的物件:

1
2
3
4
5
var obj = {
myName: 'Ray',
};

console.log(obj); // obj

在此其實你並沒有辦法透過 Chrome 直接看到物件的屬性特徵,所謂的屬性特徵是什麼意思呢?舉凡這個屬性能不能被新增、重新寫入或者是透過迴圈讀取等,而這個屬性特徵若要觀看的話,則必須使用一個語法才可以,也就是 Object.getOwnPropertyDescriptor

Object.getOwnPropertyDescriptor 的用法並不困難,第一個參數通常是物件本身,第二個則是要查看的屬性:

1
2
3
4
5
var obj = {
myName: 'Ray',
};

console.log(Object.getOwnPropertyDescriptor(obj, 'myName'));

基本上當你貼入到 Chrome 後應該會看到以下資訊

1
2
3
4
5
6
{
configurable: true,
enumerable: true,
value: "Ray",
writable: true,
}

這四個參數就是物件屬性特徵,分別為:

  • configurable - 屬性可否被設定
    • 簡單來講就是這是屬性能不能被修改 or 刪除屬性。
  • enumerable - 屬性可否被列舉
    • 最簡單來講就是,可不可以被迴圈讀取出來。
  • value - 值
  • writable - 屬性的值可否被寫入
    • 簡單來講就是能不能被修改屬性本身的值,例如 Ray 改成 Hello。

那麼前面講那麼多,我們實際上如果想針對物件的屬性調整特徵的話該怎麼做?這邊就會使用 Object.defineProperty 來調整屬性特徵,Object.defineProperty 需要帶入的參數就有三個,第一個是要調整的物件,第二個則是要調整的屬性,第三個則是屬性特徵的調整,假使來講,我希望 myName 這個屬性不可以再次被調整的話,則是這樣子使用:

1
2
3
4
5
6
7
8
9
var obj = {
myName: 'Ray',
};

Object.defineProperty(obj, 'myName', {
writable: false,
});

obj.myName = 'Hello';

雖然當你複製貼上上述程式碼到 Chrome 之後會看到回傳一個 Hello 的字眼,但實際上若你查看 console.log(obj.myName); 則會發現屬性依然是 Ray,請注意這邊是靜默錯誤,因此不會出現錯誤提示,你也可以試著將程式碼修改成以下:

1
2
3
4
5
6
7
8
9
10
11
'use strict'

var obj = {
myName: 'Ray',
};

Object.defineProperty(obj, 'myName', {
writable: false,
});

obj.myName = 'Hello';

基本上調整完之後,你就會看到錯誤訊息「Uncaught TypeError: Cannot assign to read only property 'myName' of object '#<Object>'」,白話文就是,你不能針對不可寫入的屬性做調整,目前它只可以被讀取。

反之如果你希望屬性不被刪除的話,則是這樣調整:

1
2
3
4
5
6
7
8
9
var obj = {
myName: 'Ray',
};

Object.defineProperty(obj, 'myName', {
configurable: false,
});

delete obj.myName;

當你將屬性的 configurable 設置為 false 之後,就會發現 delete 物件時是回傳一個 false

最後一個則是 enumerableenumerable 比較特別一點,這邊直接看一個範例,通常我們要讀取出物件中的屬性會使用 for 讀取出來:

1
2
3
4
5
6
7
var obj = {
myName: 'Ray',
};

for(var i in obj) {
console.log(i); // myName
}

但是如果你將 enumerable 改成 false 之後,則會直接消失變成 undefined

1
2
3
4
5
6
7
8
9
10
11
var obj = {
myName: 'Ray',
};

Object.defineProperty(obj, 'myName', {
enumerable: false,
});

for(var key in obj) {
console.log(key); // undefined
}

就算你使用較新的語法也是一樣的

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
myName: 'Ray',
};

Object.defineProperty(obj, 'myName', {
enumerable: false,
});

Object.keys(obj).forEach((key) => {
console.log(key); // undefined
})

console.log(Object.keys(obj)); // []

value 我就不再多介紹,簡單來講就是重新設置值的概念而已,當然你也可以用 defineProperty 來更新值,可是通常來講你會乾脆寫 obj.myName = 'Hello Ray' 而不太會寫 Object.defineProperty(obj, 'myName', { value: 'Hello Ray' }); (笑)。

defineProperty 額外有一個小雷點,它只能做到淺層的設置與保護,若是深層物件則沒有辦法

1
2
3
4
5
6
7
8
9
10
11
var obj = {
myName: {},
};

Object.defineProperty(obj, 'myName', {
writable: false,
});

obj.myName.a = 'Ray';

console.log(obj.myName.a); // Ray

當然 defineProperty 還有另一種大量調整的語法,假使有兩個以上的屬性特徵要調整,一個一個使用 Object.defineProperty 就會顯得很麻煩,因此就可以使用 Object.defineProperties,而寫法也非常雷同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
myName: 'Ray',
a: 'QQ'
};

Object.defineProperties(obj, {
myName: {
writable: false,
},
a: {
value: 'Hello Ray',
}
});

obj.myName = 'say Hello';

console.log(obj.myName); // Ray
console.log(obj.a); // Hello Ray

參考文獻