0%

JS核心篇-物件屬性特徵

本文快速導覽 :

  • 物件屬性設定
    • 單一屬性設定 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 只能針對當下的物件屬性作設定,若物件內屬性值也為物件,還是可以被寫入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* 物件屬性設定 */
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 一次修改多屬性設定 */
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

  • 防止該物件屬性被擴充,但它無法對巢狀物件屬性禁止
  • 屬性可以被刪除
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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
  • 無法新增刪除屬性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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 凍結後,就無法對它修改屬性設定,否則會報錯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 不會有任何作用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // 收入來源有股票與工作
    // 將它們分別設定 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 物件屬性
  3. Henry Chang - JavaScript - 屬性描述器 (2)