[day 05] getter & setter: 屬性描述器


本文同步發表於隨性筆記

本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章

No CSS! No HTML! No Browser!
Just need programming language


有了物件然後呢?
今天來說說關於成員(field/attribute/member)背後的屬性。

getter & setter

有一個家族-- 神崎家 ,生了一個小孩叫 アリア ,但生下後被 りこ 偽裝,被發現後重新命名為 アリア

如有雷同存屬巧合

var 神崎家 = class {
  constructor(name){
      this.__name = `神崎・${name}`;
    }
  static born(name){
      return new 神崎家(name)
  }
  set name(new_name){
      this.__name = `神崎・${new_name}`;
  }
  get name(){
      let first_name = this.__name.substr(0,2),
          last_name = this.__name.substr(3, this.__name.length + 1);
      return `${first_name}・H・${last_name}`
  }  
  introduce(){
      console.log(`Hi~ My name is ${this.name}`)
  }
  rename(new_name){
      this.name = new_name;
      return this.name;
  }
}
var aria = 神崎家.born("アリア");
aria.introduce(); // => Hi~ My name is 神崎・H・アリア
aria.rename("りこ"); // => 神崎・H・りこ
aria.introduce(); // => Hi~ My name is 神崎・H・りこ
aria.name = "アリア";
aria.introduce(); // => Hi~ My name is 神崎・H・アリア
console.log(aria.name); // => 神崎・H・アリア

上例中, アリア 的家族名是 神崎 ,中間名是 H 。而且 アリア 一生下來就會自我介紹。

並且可以看到,有著get描述器的函式,可以像是一般屬性一樣被存取;set可以在賦予值時做加工。透過只給get不給set,還可以做到唯讀(read only)屬性。

Object.defineProperty(神崎家.prototype, "first_name", {
    get(){return "神崎"},
    configurable: true, // 僅為了之後測試方便
});

console.log(aria.first_name); // => 神崎
aria.first_name = "遠山";
console.log(aria.first_name); // => 神崎
aria.__proto__.first_name = "遠山";
console.log(aria.first_name); // => 神崎

沒人可以亂改家族名!

為什麼我要加在神崎家.prototype上呢?
不懂嗎?去看看關於Prototype Chain繼承

作業 - 更靈活的first_name你做得到嗎?

注意到一開始把"神崎"寫死,成了一個魔術數字(magic number)了嗎?你有辦法把上面例子改的更靈活更有彈性吧?透過改寫和Object.defineProperty加點工看看吧!部份程式碼可能如下:

很顯眼?
絕對不是因為我懶得刪程式碼
是要給你提示拉!

Object.defineProperty(神崎家.prototype, "first_name", {
    get(){return this.__first_name||"神崎";},
    set(v){this.__first_name = v;},
    configurable: true, // 僅為了之後測試方便
});

accessor

除了gettersetter,還可以直接設定accessor,不過關鍵字是value

Object.defineProperty(aria, "weapons" , {
    value: ["雙槍", "雙劍"],
    writable: true,
    configurable: true, // 僅為了之後測試方便
});

console.log(aria.weapons); // => [ '雙槍', '雙劍' ]

其實也差不多就等於:

aria.rivals = ["峰 理子"]; // 對...就上面偽裝過 アリア 的人

defineProperty & getOwnPropertyDescriptor

上面例子應該夠你使用Object.defineProperty了,還是不懂嗎?去參考一下更正式的文件吧!

這以小節主要用Object.getOwnPropertyDescriptor1來看看上面設定的屬性。

首先看看一般直接設定的結果和accessor:

Object.getOwnPropertyDescriptor(aria, "weapons"); // => <BELOW>
// 不用註解有上色比較好看
{
  value: [ '雙槍', '雙劍' ],
  writable: true,
  enumerable: false,
  configurable: true
}


Object.getOwnPropertyDescriptor(aria, "rivals"); // => <BELOW>
{
  value: [ '峰 理子' ],
  writable: true,
  enumerable: true,
  configurable: true
}

可以看到最主要差別在enumerable,表示是否能用for去迭代。(點我看關於屬性描述器中的enumerable補充)


再來看看gettersetter。就不多J4了。


Object.getOwnPropertyDescriptor(aria.__proto__, "first_name"); // => <BELOW>
{
  get: [Function: get],
  set: [Function: set],
  enumerable: false,
  configurable: true
}

2019/02/28 關於屬性描述器中的enumerable補充

上面看到過關於enumerable的內容。

Object.getOwnPropertyDescriptor(aria, "weapons"); // => <BELOW>
// 不用註解有上色比較好看
{
  value: [ '雙槍', '雙劍' ],
  writable: true,
  enumerable: false,
  configurable: true
}

Object.getOwnPropertyDescriptor(aria, "rivals"); // => <BELOW>
{
  value: [ '峰 理子' ],
  writable: true,
  enumerable: true,
  configurable: true
}

關於enumerable的行為表現在物件aria的迭代上(for-loop)。不過可以先來看看Object.keys()的結果:

console.log(Object.keys(aria));
// => [ '__name', 'rivals' ]

可以看到並沒有weapons,因為enumerable: false。既然keys()找不到,也大概可以理解為什麼迭代時找不到他了。來用for-in試試:

for(key in aria){
   console.log(`${key}: ${aria[key]}`);
}

/*Output:
__name: 神崎・アリア
rivals: 峰 理子
*/

不過有個例外:當key是symbol時,不管如何都無法迭代:

Object.defineProperty(aria, Symbol("super_power") , {
    value: ["超級推理"],
    writable: true,
    enumerable: true,
    configurable: true, // 僅為了之後測試方便
});

s = Object.getOwnPropertySymbols(aria)
console.log(s); // => [ Symbol(super_power) ]
console.log(aria[s[0]]); // => [ '超級推理' ]

for(key in aria){
   console.log(`${key}: ${aria[key]}`);
}
/*Output:
__name: 神崎・アリア
rivals: 峰 理子
*/

雖然可以找得到這個屬性,但是迭代裡不會出現,也不會出現在keys()裡。

參考資料

#js #javascript #EMCAScript
寫了這麼久的JS,你還在物件之前的時代嗎?只有資料、函式可以用,破破的抽象化,不會難以維護?儘管JS起初並不以物件導向設計,但透過原形鏈設計,其仍然可以具有好維護的物件導向特色。本系列從最基礎的this,深入ES6之後的class。






Related Posts

Longitudinal Vehicle Model 實作小筆記

Longitudinal Vehicle Model 實作小筆記

【單元測試的藝術】Chap 11: 設計與可測試性

【單元測試的藝術】Chap 11: 設計與可測試性

Day04 LINE - Messaging API

Day04 LINE - Messaging API

Ceres 函式庫簡介

Ceres 函式庫簡介

GIT 基本指令

GIT 基本指令

[MTR04] W1 D4 搞懂 Git 交作業流程

[MTR04] W1 D4 搞懂 Git 交作業流程



Comments