--- tags: JavaScript, 六角筆記王 title: JavaScript 物件屬性細部設定 --- # JavaScript 物件屬性細部設定 當物件設定了屬性後,就會有物件預設的原生語法,每個屬性都能去做設定,這也是為什麼某些函式可以執行,也可以取用它不同的屬性值,因為函式也是物件。 本文快速導覽 : - 物件屬性設定 - 單一屬性設定 `Object.definedProperty(物件, 屬性)` - 多個屬性設定 `Object.defineProperties(物件, { 屬性:{}, 屬性2:{}})` - 物件設定方法 - 防止物件屬性擴充 `Object.preventExtensions(物件)` - 封裝物件 `Object.seal` - 凍結物件 `Object.freeze` - 檢查物件設定 - 回傳物件屬性設定 `Object.getOwnPropertyDescriptor(物件, 屬性)` - 回傳多個物件屬性設定 `Object.getOwnPropertyDescriptors(物件, 屬性1, 屬性2)` - 物件屬性可否被擴充 `Object.isExtensible(物件)` (true or fasle) - 物件是否被封裝 `Object.isSealed(物件)` - 物件是否被凍結 `Object.isFrozen(物件)` - 列舉物件屬性 `Object.keys(物件)` - 只回傳可被列舉的屬性名稱 - 與 `for in` 一致 - 回傳陣列 `[屬性1, 屬性2, ...]` - 列舉物件屬性 `Object.getOwnPropertyNames(物件)` - 不管是否無法被列舉,都會顯示其屬性名稱 - 回傳陣列 `[屬性1, 屬性2, ...]` - 賦值運算不使用額外函式 - Getter 與 Setter - 錯誤設定 (快速導覽包含一些本文未使用的方法,以方便日後搜尋) ## 物件屬性設定 在原型鏈中,我們會用 `某函式.prototype.要新增的屬性名稱` 在原生原型上新增共用的方法或值,但新增後使用 `console.log()` 卻找不到它,這是怎麼一回事? 原因在於它的屬性被 `Object.definedProperty` 設定了。 ### 單一屬性設定 Object.definedProperty | 可設定特徵名稱 | 功能 | | -------- | -------- | | value | 值 | | writable | 可否寫入 | | configurable | 可否被刪除 | | enumerable | 可否被列舉 | 要注意的是,`Object.definedProperty` 只能針對當下的物件屬性作設定,若物件內屬性值也為物件,還是可以被寫入。 ```javascript /* 物件屬性設定 */ var person = { a: 1, b: 2, c: 3 } Object.defineProperty(person, 'a', { value: 10, writable: false, // 不可寫入 configurable: false, // 不可被刪除 enumerable: false // 不可被列舉 }) person.a = 100 delete person.a // 可以用 for in 的方式看每個屬性名稱, // 會發現 person.a 還在 for (var key in person){ console.log(key) } console.log(person.a) // 10,不可再寫入,不能被刪除,也不能被列舉 /* 若物件內屬性值也為物件,還是可以被寫入 */ Object.defineProperty(person, 'd', { value: {}, writable: true, configurable: true, enumerable: true }) person.d.a = 1000 console.log(person.d.a) // 1000 ``` ### 多個屬性特徵設定 Object.defineProperties ```javascript /* 一次修改多屬性設定 */ var person = { a: 1, b: 2, c: 3 } Object.defineProperties(person, { b: { value: 200 }, d: { writable: false } }) person.d = [] console.log(person.b) // 200 console.log(person.d) // { a:1000 } ``` ## 物件設定方法 除了可以設定物件內屬性,也可以針對物件本身去設定,像是 : - `Object.preventExtensions` - 防止擴充 / 無法新增屬性,但無法針對巢狀屬性禁止 - `Object.seal` - 封裝 - `Object.Freeze` - 凍結 ### Object.preventExtensions - 防止該物件屬性被擴充,但它無法對巢狀物件屬性禁止 - 屬性可以被刪除 ```javascript var person = { a:1, b:2, c:3 } Object.preventExtensions(person) delete person.c // 回傳布林值,是否可被擴充 console.log('是否可以被擴充:'+ Object.isExtensible(person)) // 檢查物件屬性設定 console.log(Object.getOwnPropertyDescriptor(person, 'a')) // Object { // configurable: true, // enumerable: true, // value: 1, // writable: true // } console.log(person) // Object { // a: 1, // b: 2 // } ``` ### Object.seal - 不能調整屬性特徵 - 可以調整目前屬性值 - 物件屬性會被加上 preventExtensions - 無法新增刪除屬性 ```javascript var person = { a:1, b:2, c:3 } Object.seal(person) person.a = 1000 person.d = 10 delete person.b Object.defineProperty(person, 'b', { writable: false }) person.b = 2000 // 回傳布林值,是否被封裝 console.log('是否被封裝:'+ Object.isSealed(person)) // true // 檢查物件屬性設定 console.log(Object.getOwnPropertyDescriptor(person, 'a')) // Object { // configurable: false, // enumerable: true, // value: 1000, // writable: true // } console.log(person) // Object { // a: 1000, // b: 2000, // c: 3 // } ``` ### Object.freeze 物件使用 `Object.Freeze` 凍結後,就無法對它修改屬性設定,否則會報錯。 ```javascript var person = { a:1, b:2, c:3 } Object.Freeze(person) // 回傳布林值,是否被凍結 console.log('是否被凍結:'+ Object.isFrozen(person)) // true // 檢查物件屬性設定 console.log(Object.getOwnPropertyDescriptor(person, 'a')) // Object { // configurable: false, // enumerable: true, // value: 1, // writable: false // } ``` ## 賦值運算不使用額外函式 物件屬性還有兩個特徵可以設定,分別是 Getter 與 Setter,以下就來看看它們怎麼運作。 ### Getter 與 Setter - Getter,取值 - 使用 console.log()檢查時,狀態為 `...`,當點開時才會正確取值 - Setter,存值 - 必須要有傳入參數,否則報錯 - console.log()、return 不會有任何作用 ```javascript var a = { array: [1, 2, 3], get getData(){ return this.array }, set addData(a){ this.array.push(a) return 1 }, } a.addData = "99" a.addData = "100" console.log(a) ``` ### 使用 Object.defineProperty,設定 Getter、Setter 若有一個已知的物件其屬性未設定 Getter、Setter,可以使用 defineProperty來設定。透過這個方式設定時,Getter、Setter的預設為不可列舉也不可被刪除。以下範例使用了 : - 多個屬性特徵設定 - Getter 與 Setter ```javascript // 收入來源有股票與工作 // 將它們分別設定 Getter 與 Setter var sourceofIncome = { money: 0, stock: null, work: null } Object.defineProperties(sourceofIncome, { money: { configurable: false }, stock: { set: function (money){ this.__stock__ = money this.money += money }, get: function (){ return `股票賺了: ${this.__stock__},現在有: ${this.money}` } }, work: { set: function (money){ this.__work__ = money this.money += money }, get: function (){ return `股票賺了: ${this.__work__},現在有: ${this.money}` } }, }) sourceofIncome.stock = 20000 sourceofIncome.work = 50000 // 若分段顯示就會是分段的,現在金額顯示為累計過的 console.log(sourceofIncome.stock) console.log(sourceofIncome.work) console.log(sourceofIncome.money) ``` - 若要設定屬性值,可以針對屬性再多一個屬性,像是 `this.__work__`,否則指向自己,會變成無限循環。 - 取用屬性時,可以同時做很多事,不用再額外寫函式傳參數進去。 ### 錯誤設定 設定過程中可能會遇到以下問題 : 1. `Maximum call stack size exceeded` - Getter 與 Setter 不能指向自己,否則會造成無限循環。 2. `Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>` - 因為屬性特徵不相容 - 資料描述器 ( data descriptor ) : value、writable與其他 - 存取器描述器 ( accessor descriptor ) : get 與 set ## 參考來源 > 1. 六角學院 - JS核心篇 > 2. [andyyou - 深入 ECMAScript 5 物件屬性](https://andyyou.github.io/2017/10/28/javascript-object-property/?fbclid=IwAR1Uh6dwUEPz-fl7iJQaFM9dj7elVzCE0QjTdpdSYLq6UY-v4LFAH1Q_ovU) > 3. [Henry Chang - JavaScript - 屬性描述器 (2)](https://ithelp.ithome.com.tw/articles/10197827?sc=pt)