[JavaScript] Symbolによるprivateを現実的にする


はじめに

#を付ける方法は、対応ブラウザが少ないため、#を使用せずにprivateを実現する方法として、こちらの記事を参考にさせて頂きました。
JavaScriptでprivate「Symbol」編

問題点

  • クラスを定義する前にprivateなプロパティ名を列挙しなければならない
  • publicとprivateで書き方がだいぶ違う
  • クラス作るたびにアロー関数作ってシンボル作ってとやることが多い
  • 完全なprivateじゃない

上記の記事では、これらの問題点が挙げられていました。

解決策

これらの問題点は「完全なprivateじゃない」を除き

  • Symbolの保持
  • 保持したSymbolの参照

これらの書き方が異なることに起因しています。
つまり、これらの書き方を1つに統一することが出来れば、問題は解決すると言えます。

「完全なprivateじゃない」については

delete Object.getOwnPropertySymbols

このように記述しておくことで、Hard privateを実現出来なくは無いので特に問題はありません。

シンプルなSymbol処理

const PrivateModifier = function() {
  return new Proxy({}, {
    get(obj, name) {
      if (obj[name] === undefined) {
        obj[name] = Symbol(name)
      }
      return obj[name]
    },
    set() {
      throw TypeError("PrivateModifier is read only.")
    }
  })
}

解説

通常、objectは存在しないプロパティにアクセスした場合、undefinedを返します。
Proxyを利用することで、デフォルトの挙動を変更出来ます。

PrivateModifierは、存在しないプロパティにアクセスした際、Symbolを動的に設定するobjectを作成します。

例えば

const _ = {}
_.hoge = Symbol("hoge")
console.log(_.hoge) // Symbol(hoge)
_.huga = Symbol("huga")
console.log(_.huga) // Symbol(huga)
const _ = new PrivateModifier
console.log(_.hoge) // Symbol(hoge)
console.log(_.huga) // Symbol(huga)

これらは同じ意味になります。

値はこのようになります。

const _ = new PrivateModifier
console.log(_.hoge === _.hoge) // true

使用方法

パターン1 (prototype)

const ClassName = (function() {
  const _ = new PrivateModifier

  // constructor
  function ClassName(privateValue, publicValue) {
    // private instance
    this[_.privatePropertyName] = privateValue
    // public instance
    this.publicPropertyName = publicValue
  }

  // alias
  const proto = ClassName.prototype

  // private prototype
  proto[_.privateMethodName] = function() { }
  // public prototype
  proto.publicMethodName = function() { }

  return ClassName
})()

パターン2 (class)

const ClassName = (() => {
  const _ = new PrivateModifier

  return class ClassName {
    constructor(privateValue, publicValue) {
      // private instance
      this[_.privatePropertyName] = privateValue
      // public instance
      this.publicPropertyName = publicValue
    }

    // private prototype
    ;[_.privateMethodName]() { }
    // public prototype
    publicMethodName() { }
  }
})()

対応状況

  • IE以外のブラウザで使用できます