[day08] Closure & Private:番外短篇 隱私成員


本文同步發表於隨性筆記

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

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


這篇是臨時起意補的一篇短篇,用於示例如何模擬私有屬性。儘管這可能不是JS主流設計思想方法,但知道相信對你沒壞事。

在第5天-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;
  }
}

可能會有人問...為什麼__name不直接這樣寫就好:

var Person = class{
    __name = "Default"
    constructor({name, famile_name = "noname"} = {}){
        this.__name = name;
        this.__famile_name = famile_name;
    }

    hello(){
        console.log(`Hello, ${this.name}`);
    }

    get name(){
        return `${this.__famile_name} の ${this.__name}`;
    }

    set name(new_name){
       this.__name = new_name;
    }
}

var boy = new Person({name:"逆迴十六夜"})
boy.hello(); // => Hello, noname の 逆迴十六夜
console.log(boy.name); // => noname の 逆迴十六夜
boy.name = "久遠 飛鳥"
console.log(boy.name); // => noname の 久遠 飛鳥
boy.name = "久遠 飛鳥"

好拉久遠 飛鳥不是boy

也就是直接把__name寫在class裡就好,要寫在constructor。阿...我只能說沒有理由......只是我一開始寫JS的OOP是用工廠模式。(可以回去看看第二天的內容)

還有一部份,因為我寫習慣Python了XD

上面更好的寫法可能會是:

var Person = class{
    __name = "Default"
    static family_name = "noname"
    constructor(name){
        this.__name = name;
    }

    hello(){
        console.log(`Hello, ${this.name}`);
    }

    get name(){
        return `${Person.family_name} の ${this.__name}`;
    }

    set name(new_name){
        this.__name = new_name;
    }

    get family_name(){
        return Person.family_name;
    }
}

var boy = new Person("逆迴十六夜")
boy.hello(); // => Hello, noname の 逆迴十六夜
console.log(boy.name); // => noname の 逆迴十六夜
console.log(boy.family_name); // => noname

實際上,boy並沒有family_name而是在Person上,實現了天下一家親。

Object.getOwnPropertyNames(boy) // => [ '__name' ]
Object.getOwnPropertyNames(Person) // => [ 'length', 'prototype', 'name', 'family_name' ]
Object.getOwnPropertyNames(Person.prototype) // => [ 'constructor', 'hello', 'name', 'family_name' ]

好像扯遠了。

用閉包實現私有屬性(closure & private)

用工廠模式有一個好處,可以做很多加工。像是可以透過閉包實現類似私有屬性,讓其他物件無法直接存取資料。

var Noname = class{
    static family_name = "noname"
    constructor(__name){
       var name = __name;
       Object.defineProperty(this, "name", {
           set(new_name){
               name = new_name;
           },
           get(){
               return `${Person.family_name} の ${name}`;
           }
       });

       this.hello = function(){
          console.log(`Hello, ${this.name}`);
       }
    }

    get family_name(){
        return Person.family_name;
    }
}

var boy = new Noname("逆迴十六夜")
boy.hello(); // => Hello, noname の 逆迴十六夜
console.log(boy.name); // => noname の 逆迴十六夜
console.log(boy.family_name); // => noname

好了,這下不但沒法用boy去修改family_name,也不能存取__name了(因為根本沒有)。在constructorvar name,可以在constructor被定義的方法裡共享、保存,這是閉包(closure)。而外部無法存取,進而實現類似private的概念。如果想要個方法間還不共享變數,大可以再多做加工。不過不稍微共享,我覺得意義不大。

來仔細看看實現結果

儘管上面實現也差不多是本篇最主要說的內容了,但還有些東西是使用這方式你必須知道的:

這樣加工的方法,並不是普通方法

這樣加工的方法,並不是普通方法。與其他方法依附在.prototype上不同,這樣的方法直接依附在實例上。

Object.getOwnPropertyNames(Person.prototype) // => [ 'constructor', 'hello', 'name', 'family_name' ]
Object.getOwnPropertyNames(Noname.prototype) // => [ 'constructor', 'family_name' ]
Object.getOwnPropertyNames(boy) // => [ 'constructor', 'hello', 'name', 'family_name' ]

Noname.prototype不再有hello()的方法,而是出現在boy下。這意為著你有多少實例,就存在多少方法,這可能提高記憶體的使用量。

當然上例的hello()我是故意這樣寫的,只是要提醒你,使用工廠模式,或是prototype-based的方式處理,你應該清楚在做什麼,在設計什麼。

※ Note: TypeScripte的private關鍵字好像是在編譯時檢查,而不做運行時保護。實際上並不同於本篇實現的概念。

本篇完~灑花~~

#js #javascript #EMCAScript