在JavaScript中,你可以如下所示建立一個物件字面量:
1 2 3 4 |
var cat = { name: 'foo', age: 9 }; |
乍一看,好像物件cat有字串和數字值這兩個屬性。然而,這不僅僅是JavaScript直譯器。在ES5中,介紹了屬性描述符的概念。在我們繼續討論屬性描述符之前,讓我們試著回答幾個問題:
- 如何建立只讀屬性?
- 如何制定不可列舉的屬性?
- 如何使屬性不可配置?
- 如何確定一個屬性是否是隻讀的?
如果你理解屬性描述符,那麼你就可以回答所有這些問題。
請看下面的程式碼:
1 2 3 4 5 6 |
var cat = { name: 'foo', age: 9 }; var a = Object.getOwnPropertyDescriptor(cat, 'name'); console.log(a); |
輸出將如下所示:
正如你在這裡看到的,這個屬性有四個特徵:
value儲存屬性的資料,而writable,enumerable和configurable則描述屬性的其他特徵。接下來我們將對這些特徵一一闡述。
writable
屬性的值是否可以更改是由writable特徵決定的。如果writable設定為false,那麼屬性的值不能更改,JavaScript將忽略屬性值中的任何更改。請看下面的程式碼:
1 2 3 4 5 6 7 8 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { writable: false }); console.log(cat.name); // foo cat.name = "koo"; // JavaScript will ignore it as writable is set to false console.log(cat.name); // foo |
你可以使用Object.defineProperty更改writable、enumerable和configurable特徵的值。我們稍後會在這篇文章中詳細討論Object.defineProperty,但正如你在上面的程式碼片段中看到的那樣,我們已經將writable屬性設定為false,從而改變了name屬性的值。JavaScript將忽略重新分配,並且name屬性的值將保持為foo。
如果以嚴格模式執行程式碼,那麼為了重新分配writable設定為false的屬性值,JavaScript將丟擲異常。請看下面的程式碼:
1 2 3 4 5 6 7 8 |
'use strict'; var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { writable: false }); console.log(cat.name); // foo cat.name = "koo"; // error |
在這裡,JavaScript以嚴格模式執行,因此,當你重新分配name屬性的值時,JavaScript將丟擲異常,如下所示:
這裡的錯誤訊息說,你不能賦值到只讀屬性。也就是說,如果屬性的writable特徵設定為false,那麼屬性將充當只讀屬性。
configurable
屬性的其他特徵是否可以配置取決於configurable的值。如果屬性configurable設定為false,則不能更改writable和enumerable的值。請看下面的程式碼:
1 2 3 4 5 6 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { configurable: false }); Object.defineProperty(cat, 'name', { enumerable: false }); |
在這裡,我們將name屬性的configurable設定為false。之後,我們將enumerable設定為false。如前所述,一旦一個屬性的configurable設定為false,那麼你就不能改變另一個特徵。
對於上面的程式碼,JavaScript會丟擲一個TypeError異常,如下圖所示。你會得到無法重新定義屬性名稱的錯誤:
在使用configurable的時候,你需要記住,改變configurable的值只能做一次。如果將屬性的configurable設定為false,那麼你就不能重新分配它;你無法撤消對configurable的更改。請看下面的程式碼:
1 2 3 4 5 6 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { configurable: false }); Object.defineProperty(cat, 'name', { configurable: true }); |
我們在重新分配name屬性的configurable,但是,JavaScript會對上述操作丟擲一個TypeError,如下圖所示。正如你所看到的,一旦configurable被設定為false,就不能撤銷那個更改。
另一個重要的事情是,即使configurable設定為false,writable也可以從true更改為false——但反之則不然。請看下面的程式碼:
1 2 3 4 5 6 7 8 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { configurable: false }); Object.defineProperty(cat, 'name', { writable: false }); cat.name = 'koo'; console.log(cat.name); // foo |
如果不是在嚴格模式下,上面的程式碼不會丟擲任何異常。正如我們前面所討論的,即使configurable為false,writable也可以從true變為false,反之則不然。另一個需要牢記的重要事項是,你無法刪除configurable設定為false的屬性。
1 2 3 4 5 6 7 8 9 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { configurable: false }); delete cat.name; // wont delete as configurable is false console.log(cat.name); // foo delete (cat.age); // will be deleted console.log(cat.age); // undefined |
在上面的程式碼中,你會發現JavaScript不會刪除name屬性,因為name屬性的configurable設定為false。
enumerable
對於一個屬性,如果你設定了enumerable:false,那麼這個屬性將不會出現在列舉中,因此它不能用在諸如for … in迴圈這樣的語句中。
請看下面的程式碼:
1 2 3 4 5 6 7 8 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { enumerable: false }); for (let f in cat) { console.log(f); // will print only age } |
在這裡,你只能得到age,因為name的enumerable被設定為了false。這是另一個需要記住的重要事項:通過設定enumerable:false,唯一的屬性將不可用於列舉。我們來看下面的程式碼:
1 2 3 4 5 6 7 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { enumerable: false }); console.log(cat.name); // foo console.log('name' in cat); // true |
在這裡,name屬性enumerable設定為false,但你可以訪問它。在檢查name是否屬於cat的屬性時,你也會發現是true。
有時,你可能需要檢查某個特定屬性enumerable是否設定為false或true。你可以通過使用propertyIsEnumerable方法來做到這一點:
1 2 3 4 5 6 |
var cat = { name: 'foo', age: 9 }; Object.defineProperty(cat, 'name', { enumerable: false }); console.log(cat.propertyIsEnumerable("name")); // false |
結論
作為一名專業的JavaScript開發人員,你必須對JavaScript物件屬性描述符有一個很好的理解,我希望你能從這篇文章中學到一些知識!請繼續關注我們的下一篇文章,繼續學習JavaScript中更重要的概念。