---
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)