JavaScriptのクラスでprivateを実現してみたかった人生だった - 生涯未熟

生涯未熟

プログラミングをちょこちょこと。

JavaScriptのクラスでprivateを実現してみたかった人生だった

というわけでJavaScirptのクラス内にprivateメソッドやらprivateプロパティやらを実現したいな、と思って調べた時に色々知ることがあったのでまとめてみる。

主な実装方法

調べてみると4つほど方法がありました。

  • privateメソッド・プロパティの接頭辞に_を付ける
  • クロージャを使った実装
  • Symbolを使う
  • WeakMapを使う

それぞれについてうんうんと考えてみましょう。

privateメソッド・プロパティの接頭辞に_を付ける

これは超簡単ですね。

以下の様な感じ。

var Hoge = function(){};
Hoge.prototype = {
  publicMethod: function() { console.log('パブリックメソッドだよ!') },
  _privateMethod: function() { console.log('プライベートメソッドだよ!') }
}

var hoge = new Hoge();
hoge.publicMethod();
hoge._privateMethod(); // プライベートメソッドなのに呼べちゃう

これはただの紳士協定で、「プライベートメソッドは接頭辞にアンダースコアを付けるので外から呼ばないでね」って感じのものです。

ゆるい。

クロージャを使った実装

これはこちらの記事にとても詳しく書いてあります。 (@jun1s様に感謝)

sites.google.com

メモリ消費の観点から現実的では無い模様。

Symbolを使う

SymbolはES6で新たに追加されたプリミティブなデータ型ですね。

これを使って果たしてprivateを実現できるのでしょうか。

とりあえずやってみました。

class Hoge {
  constructor() {
    let privateProperty = Symbol['privateProperty']
    let privateMethod = Symbol['privateMethod']
  }
  
  setFoo() {
    this[this.privateProperty] = 'foo'
  }
  
  getFoo() {
    return this[this.privateProperty]
  }
  
  setBar() {
    this[this.privateMethod] = function() {
      return 'bar'
    }
  }
  
  getBar() {
    return this[this.privateMethod]()
  }
}

let hoge = new Hoge()
hoge.setFoo()
console.log(hoge.getFoo())
hoge.setBar()
console.log(hoge.getBar())

これでHogeクラスにprivateプロパティ・メソッドが実現できました。

ただし、Symbolを使った方法は1点問題があり、

  • そもそもprivateじゃない

という問題点があります。 どういうことでしょうか?

そもそもprivateじゃない問題

実は this[this.sym] で設定した値を外から取得する方法があります。

それは getOwnPropertySymbols を使うとSymbolが取得できてしまうというもの。

ただ、手元でやってみた結果Symbolが取得できませんでした・・・

何故に。

class Hoge {
  constructor() {
    let sym = Symbol['privateMethod']
  }
  
  setFoo() {
    this[this.sym] = 'foo'
  }
  
  getFoo() {
    return this[this.sym]
  }
}

let hoge = new Hoge()
hoge.setFoo()
console.log(Object.getOwnPropertySymbols(hoge))

// 結果
// []

参考:

qiita.com

というわけでSymbolを用いた方法もちょっと違うかなと。

[追記]

@gaogao_9さんからアドバイスを頂けました。

ご教授頂いたコードがこちら。

const Hoge = (()=> {
  const privateProperty = Symbol('privateProperty')
  const privateMethod   = Symbol('privateMethod')
  
  class Hoge {
    constructor() {
    }
    
    set foo(value){
      this[privateProperty] = value
    }
    
    get foo(){
      return this[privateProperty]
    }
    
    [privateMethod](){
      return 'bar'
    }
    
    get bar() {
      return this[privateMethod]
    }
  }
  
  return Hoge;
})();

let hoge = new Hoge()
hoge.foo = 'foo'
console.log(hoge.foo)
console.log(hoge.bar())

こちらの方が見やすいですね!

WeakMapを使う

WeakMapというMapのキーにオブジェクトが使える方法があるので、それを使ってprivateを実現する。

const privateMap = new WeakMap()
function getPrivates(self) {
  let p = privateMap.get(self);
  if (!p) {
    p = {};
    privateMap.set(self, p);
  }
  return p;
}

class Hoge {
  constructor() {
  }
  
  setFoo() {
    getPrivates(this).foo = 'foo'
  }
  
  getFoo() {
    return getPrivates(this).foo
  }
  
  setBar() {
    getPrivates(this).bar = function() {
      return 'bar'
    }
  }
  
  getBar() {
    return getPrivates(this).bar()
  }
}

let hoge = new Hoge()
hoge.setFoo()
console.log(hoge.getFoo())
hoge.setBar()
console.log(hoge.getBar())

WeakMapでclassをセットして、そこにprivateプロパティなりメソッドなりをセットするような感じ。

getPrivates(hoge).foo とかで取れちゃうじゃん!って思われるかもしれませんが、実際にはclass部分を export default class とかで切り出して、 実行部は別にするので大丈夫。

まとめ

なんとなーく薄い理解ですが、WeakMapを使えばprivateが実現できますが深いことを考えなければSymbolでもいいのかなと。

更に深いことを考えなければアンダースコア使っちゃってもいいのかなと。

この辺りは実装者によってだいぶ分かれそうですね。

なんかふんわり理解をまとめた感じなので、「これ違うだろ」というツッコミ頂けると喜びます。