---
# System prepended metadata

title: JavaScript 物件屬性細部設定
tags: [' 六角筆記王', JavaScript]

---

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