Object.defineProperty 可能大家不是很常用到,但其實它無所不在,甚至於許多 mvvm, mvc 的資料繫結都是透過此接口達成的.

像是以下這段簡單的 code

var person = { name: 'Camel' };
Object.getOwnPropertyDescriptor(person, 'name');
// {value: "Camel", writable: true, enumerable: true, configurable: true}

其實就等價於

var person = {};
Object.defineProperty(person, 'name', {
  value: 'Camel',
  configurable: true,
  writable: true,
  enumerable: true
});
Object.getOwnPropertyDescriptor(person, 'name');
// {value: "Camel", writable: true, enumerable: true, configurable: true}

可以發現 object.defineProperty 有幾個可設定的 descriptor

接下來本文將針對這幾個設定的使用及應用場景做個說明


configurable: 預設為 false

已定義的 descriptor 是否可被更改 (writable:true 除外),以及 property 是否可被 delete

常見的例子是設定為 configurable:false 避免設定的 property 被 delete

e.g.

var person = {};
Object.defineProperty(person, 'name', {
  value: 'Camel',
  configurable: false,
});
delete person.name // false
console.log(person.name); // Camel

configurable:false 的例子其實蠻常見的,像是 browser 內建的 location

Object.getOwnPropertyDescriptor(window, 'location');
// {value: Location, writable: true, enumerable: true, configurable: false}

writable: 預設為 false

常見的是與 configurable:false 搭配使用,可以避免 descriptorvalue 被做任何的修改.(進階一點的方法可以用待會介紹的 getter)

var person = {};
Object.defineProperty(person, 'name', {
  value: 'Camel',
  configurable: false,
  writable: false
});
person.name = 'Penny';
console.log(person.name); // Camel

enumable: 預設為 false

此 property 是否可以被列舉(for… in/Object.keys)

常用作於類似 prototype chain 的方式使用,用來設置一個 property 來存不希望被列舉的值.

var person = {};
Object.defineProperty(person, 'name', {
  value: 'Camel',
  enumerable: false
});
console.log(Object.keys(person)); // []
console.log(Object.getOwnPropertyNames(person)) // ["name"]

除了以上介紹的三個 configurable , writable, enumable ,其實另外有兩個非常重要的 get, set 能幫助你做到更多的事,繼續看看下面的例子吧 ~

get: 預設為 undefined

當存取此變數時會呼叫的 function,回傳值會作為存取此 property 的值.

直覺想到的簡單應用可以用做資料格式化

e.g.

var person = { firstName: 'Camel', lastName: 'Chang' };
Object.defineProperty(person, 'fullName', {
  get() { return this.firstName + ' ' + this.lastName; }
});
console.log(person.fullName); // Camel Chang

set: 預設為 undefined

當設定值時會呼叫的 function,呼叫時會帶要設定的值為參數

應該蠻容易想到 set 相當適合用來做資料的 validation

e.g.

var person = {};
Object.defineProperty(person, 'discount', {
  get() { return this._discount || 95; },
  set(value) { return this._discount = value >= 80 ? value : 80; }
});
console.log(person.discount); // 95
person.discount = 75;
console.log(person.discount); // 80

其實仔細想想,你會發現 getter , setter 可以用在製作簡單的雙向資料繫結

像是 get 取值時先看看 dom 節點的狀態(view)決定值(model)

或是 set 設值前根據值(model)改變 dom 節點(view)

這篇就不細說太多複雜的實作,僅做簡單的概念與範例介紹.

順帶一提,其實 Object.defineProperty 所能提供的接口較為單純,你可能比較難區分是做 add/update/delete 的操作,當初在 Chrome 36 時有提出一個過渡的接口 Object.observe ,但後來僅有 Chrome 實作也就漸漸的 deprecated.

取而代之的是在 ES6 有另一個 Proxy 代理物件,可以讓你介接更多原生的接口,有機會的話大家可以先看看,或許之後會為 Proxy 寫篇文章 XD


參考資料:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

https://stackoverflow.com/questions/22658488/object-getownpropertynames-vs-object-keys

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/observe

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Leave a Reply