--- tags: Javascript --- # JS屬性特徵 ## 屬性特徵是甚麼 我們可以使用`Object.defineProperty`調整屬性特徵,但`defineProperty`不能對子屬性造成限制,屬於**淺層保護** 可以調整特徵為: 1. 值 1. 可否寫入 1. 可否被刪除 1. 可否被列舉 此種用法比較常在大型專案使用到,例如[Vue](https://github.com/vuejs/vue/blob/f7d85976371efb8f868f069625727acd0ec412cf/src/core/observer/index.js#L157) 列舉以下範例說明,可調整物件屬性的特徵,如果`writable`為`false`,對此屬性進行賦值,將會失敗,會有靜默的錯誤,可以在**嚴格模式**下查看,如想一次定義大量屬性特徵,可以使用`defineProperties` ```javascript= var example = { a: 1, b: 2, c: 3 } // 可設定物件屬性特徵 Object.defineProperty(example, 'a', { value: 4, // 設值 writable: false, // 可否寫入 configurable: true, // 可否被刪除 enumerable: true // 可否被列舉 }) // 賦值 example.a = 10; // 刪除 delete example.a // 列舉 for (var key in example){ console.log(`列舉${key}`) } (function(){ 'use strict'; example.a = 10; // 報錯: 不可對於不可寫入的值進行賦值 }()) // 定義大量屬性範例 Object.definePropreties(example, { a: { configurable: false, // 可否被刪除 }, b: { writable: false, // 可否寫入 } }) ``` ## 屬性延伸方法 ### 防止擴充`preventExtensions` [`Object.preventExtensions()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) 可用來避免物件被新增新的屬性,物件如果可以被增加新的屬性,我們稱它可以被擴充`(extensible)`。 `Object.preventExtensions()` 標註物件使它無法被擴充,所以在它被標註為無法擴充當下,它將無法再增加新的屬性。 ```javascript= var example = { a: 1, b: 2, c: 3 } Object.preventExtensions(example); console.log(`是否可被擴充${Object.isExtensible(example)}`); // 是否可被擴充false // Object.defineProperty throws 當為無法擴充的物件增加屬性 Object.defineProperty(example, 'new', { value: 8675309 }); // throws a TypeError ``` ### 封裝`seal` [MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/seal): `Object.seal()`方法封閉一個對象,阻止添加新屬性並將所有現有屬性標記為不可配置。當前屬性的值只要原來是可寫的就可以改變。 簡單來說就是無法新增刪除擴充,也無法配置特性,但如果原屬性值可寫入就可調整該屬性值 ```javascript= var example = { a: 1, b: 2, c: 3 } Object.seal(example); console.log(`是否可被擴充${Object.isExtensible(example)}`); // 是否可被擴充false Object.defineProperty(example, 'new', { value: 8675309 }); // 錯誤不可擴充 // 可改變屬性值 example.a = 111; console.log(example.a); // 輸出: 111 ``` ### 凍結`freeze` [MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze): `Object.freeze()` 方法可以凍結一個對象。一個被凍結的對象再也不能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對像已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對像後該對象的原型也不能被修改。 `freeze()` 返回和傳入的參數相同的對象。 簡單來說就是,物件加上`seal`,並且無法更改值,原型也不能被修改。 ```javascript= var example = { a: 1, b: 2, c: 3 } Object.freeze(example); console.log(`是否可被擴充${Object.isExtensible(example)}`); // 是否可被擴充false Object.defineProperty(example, 'new', { value: 8675309 }); // Cannot define property new, object is not extensible example.a = 111; // 嚴格模式下會拋出錯誤 console.log(example.a); // 輸出: 1 ``` ## 屬性列舉與原型關係 如果我們使用建構函式建立一個物件,使用`for`將物件屬性輸出出來看的時候,會發現原型的屬性也一併輸出出來, 為什麼會如此可以查看他們原型的屬性,`joe`的name屬性特徵,可以發現它是可以被列舉的,因此才會輸出 ```javascript= function Person() {}; Person.prototype.name = "人類"; // 可將name設為不可列舉 Object.defineProperty(Person.prototype, 'name', { enumerable: false // 可否被列舉 }) var joe = new Person(); joe.a = 1; // 會輸出a name for (var key in joe){ console.log(`key:${key}`); } console.log(Object.getOwnPropertyDescriptor(joe.__proto__, 'name')); // 查看name屬性特徵,可以發現可列舉 console.log(Object.getOwnPropertyDescriptor(joe.__proto__.__proto__, 'toString')); // 查看toString屬性特徵,可發現不可列舉 // 如果只需要當前物件屬性,可以在迴圈使用hasOwnProperty確認, // 或是將屬性enumerable設為false for (var key in joe){ if (joe.hasOwnProperty(key)){ console.log(`key:${key}`); } } ``` ## Getter和Setter [`set`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/set) 語法會在物件屬性被嘗試定義時,將其屬性綁定到要呼叫的函式內。 [`get`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/get) 語法會將物件屬性,綁定到屬性被檢索時,所呼叫的函式。 從以下範例可以看到可以透過`set`跟`get`賦值運算不使用函式 ```javascript= var wallet = { total: 100, set save(price) { this.total = this.total + price - 5; }, get save() { return this.total / 2; } } wallet.save = 500; console.log(wallet); ``` ![](https://i.imgur.com/XEYKDD4.jpg) 也可以使用`Object.defineProperty`的方式賦予 ```javascript= var wallet = { total: 100, } Object.defineProperty(wallet, 'save', { set: function(price) { this.total = this.total + price - 5; }, get: function() { return this.total / 2; } }) wallet.save = 500; console.log(wallet); console.log(Object.getOwnPropertyDescriptor(wallet, 'save')); ``` 輸出如下可以看到屬性特徵被設為不可刪除及不可列舉,如有需要也可在`defineProperty`將他更改為`true` ![](https://i.imgur.com/FlRiDq9.jpg) 也可以在陣列原型上使用 ```javascript= var lists = [1, 2, 3]; Object.defineProperty(Array.prototype, 'latest', { get: function() { return this[this.length - 1]; } }) console.log(lists.latest); // 3 ```