JavaScriptでprivate「WeakMap」編

# JavaScriptでprivate「WeakMap」編
コンストラクタの闇とそれを使った private (本当はprotectedだった)について書いたら、Symbolでprivate実現できると教えていただきました。
調べてみたらいくつかの方法でそれっぽいことができるようなので勉強がてら整理してみた(何番煎じだよ！！)
今回はWeakMap編です

コンストラクタの闇↓
[JavaScriptのコンストラクタとかいう闇深いものとprivate](https://qiita.com/schrosis/items/328eac06e7e28a9572dc)
Symbol編↓
[JavaScriptでprivate「Symbol」編](https://qiita.com/schrosis/items/edfcd7e6a6ad59a36e08)

## WeakMapとは
>WeakMap オブジェクトは、キーはオブジェクトのみで、値は任意の値にできるキー / バリューのペアからなるコレクションです。
Map オブジェクトとの違いの１つは、WeakMap のキーは列挙可能ではないことです（すなわち、キーのリストを取得するメソッドがありません）。もしも列挙可能であれば、リストは非決定性をもたらす、ガベージコレクションの状態に依存することになってしまいます。
[WeakMap](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
[キー付きコレクション](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Keyed_collections)

**オブジェクトをキーにして値を持たせる**ことができるらしいです

```js
const wm = new WeakMap
var objA = {}
var objB = {}
var objC = {}

// setメソッドで値を格納
wm.set(objA, 'hoge')
wm.set(objB, 'fuga')

// getメソッドで値を取得
console.log(wm.get(objA))
// hoge
console.log(wm.get(objB))
// fuga

// setしていないものを取得する
console.log(wm.get(objC))
// undefined

// オブジェクト以外でsetする
console.log(wm.set('string', 'error'))
// Uncaught TypeError: Invalid value used as weak map key

```

なるほど認証機能の付いた四次元ポケットのような感じですね

```js
const pocket = new WeakMap
var doraemon = new class Doraemon{}
var nobita = new class Nobita{}

pocket.set(doraemon, [
    'どこでもドア',
    'タケコプター',
])

console.log(pocket.get(doraemon))
// ["どこでもドア", "タケコプター"]
console.log(pocket.get(nobita))
// undefined

// WeakMapインスタンスをコンソールに出力して中身を確認することができる
console.log(pocket)
// WeakMap {Doraemon => Array(2)}
//  __proto__: WeakMap
//  [[Entries]]: Array(1)
//   0: {Doraemon => Array(2)}
//    key: Doraemon {}
//    value: (2) ["どこでもドア", "タケコプター"]
//   length: 1
```

オブジェクト自体には値を持たせないでWeakMap内にそのオブジェクトに紐付いた値を持たせることができました
これがWeakMapを用いたprivateの基礎です

ポイントはsetしたオブジェクトと**同じオブジェクト**でgetメソッドを使わないと値を取得できないということです


## privateプロパティの作り方

privateにするプロパティをWeakMapの中に閉じ込めます

**手順1**
WeakMapインスタンスのスコープがグローバルだと外からアクセスされてしまうので、クラス定義を返すアロー関数を即時実行させる

**手順2**
WeakMapインスタンスはclass定義の中でどこからでもアクセスできないといけないので、class定義より上で初期化する

**手順3**
継承されることを考慮して、コンストラクタ内でWeakMapの初期化(WeakMapに空のオブジェクトをsetする)処理を書くと複数回初期化されてしまうので、クラス定義より上にWeakMapへの登録と値の取得ができる関数を用意する

**手順4**
WeakMapにプライベートなプロパティを登録する

```js:クラス構文
const Hoge = (() => {
    // WeakMapインスタンス化
    const wm = new WeakMap()

    // 登録されているインスタンスであればその値を。
    // 登録されていなければ空オブジェクトを登録しその値を返す関数
    const privates = function(instance) {
        return wm.get(instance) || wm.set(instance, {}).get(instance)
    }

    return class Hoge {
        constructor() {
            // プライベートプロパティ
            privates(this)._privateProperty = 'プライベート'

            // プライベートなgetter setter
            Object.defineProperty(privates(this), 'privateProperty', {
                get: function() {
                    return privates(this)._privateProperty
                }.bind(this),
                set: function(arg) {
                    privates(this)._privateProperty = arg
                }.bind(this)
            })

            // プライベートメソッド
            privates(this).privateMethod = function() {
                return privates(this).privateProperty
            }.bind(this)
        }

        publicMethod() {
            return privates(this).privateMethod()
        }

    }
})()

var hoge = new Hoge
console.log(hoge)
// Hoge {}
//  __proto__:
//   constructor: class Hoge
//   publicMethod: ƒ publicMethod()
//   __proto__: Object

console.log(hoge.publicMethod())
// プライベート
```

```js:関数構文の場合
const Hoge = (() => {
    const wm = new WeakMap()
    const privates = function(instance) {
        return wm.get(instance) || wm.set(instance, {}).get(instance)
    }

    function Hoge() {
        // プライベートプロパティ
        privates(this)._privateProperty = 'プライベートプロパティ'

        // プライベートなgetter setter
        Object.defineProperty(privates(this), 'privateProperty', {
            get: function() {
                return privates(this)._privateProperty
            }.bind(this),
            set: function(arg) {
                privates(this)._privateProperty = arg
            }.bind(this)
        })

        // プライベートメソッド
        privates(this).privateMethod = function() {
            return privates(this).privateProperty
        }.bind(this)
    }

    Hoge.prototype.publicMethod = function() {
        return privates(this).privateMethod()
    }

    return Hoge
})()

```

 - プライベートプロパティ
  - **手順3**の関数(`privates()`)によって取得した値に格納する
 - プライベートメソッド
  - **手順3**の関数(`privates()`)によって取得した値に格納する
  - つまりどこかしらのメソッド内に書かないといけない(今回はコンストラクタ)
  - ※`bind()`しておかないとWeakMapに登録した値が`this`になる
 - プライベートな`getter` `setter`
  - **手順3**の関数(`privates()`)によって取得した値に`Object.defineProperty()`を使う
  - つまりどこかしらのメソッド内に書かないといけない(今回はコンストラクタ)
  - ※`bind()`しておかないとWeakMapに登録した値が`this`になる

※`privates()`関数の名前は`_`でもなんでもいい

見ての通りprivateな**プロパティ**ではなく、インスタンスとは**別の場所**にプロパティを隠している。

## 継承

アロー関数がクラスの定義を返しているので継承できる
また、アロー関数の中にWeakMapと値を取得する関数を閉じ込めることで、子クラスからもアクセスできないようになっている

```js
const Parent = (() => {
    const wm = new WeakMap() // Parentクラスのプライベートプロパティはここへ格納される
    const privates = function(instance) {
        return wm.get(instance) || wm.set(instance, {}).get(instance)
    }

    return class Parent {
    }
})()

const Child = (() => {
    const wm = new WeakMap() // Childクラスのプライベートプロパティはここへ格納される
    const privates = function(instance) {
        return wm.get(instance) || wm.set(instance, {}).get(instance)
    }

    return class Child extends Parent {
    }
})()
```

## 良い点
 - 外からインスタンスをコンソールに出力してもプライベートな値は見えない
  - ステップ実行してそのクラスのメソッド内で`console.log(privates(this))`ってされると見られてしまう

## 問題点
 - デバッグしにくい
 - publicとprivateで書き方がだいぶ違う
 - クラス作るたびにアロー関数作ってWeakMapのインスタンス用意してとやることが多い
 - プライベートメソッドを定義するにはどこかのメソッド内で定義しないといけない
  - ひとつインデントされて可読性が低い
  - インスタンス化するたびにメソッドの定義をするので重そう
  - どうにかできる方法があれば教えてください！
 - privateなstaticメソッドが作れない
  - どうにかできる方法があれば教えてください！
 - privateなasyncメソッドを作るには更に1手間加えないといけない

```js:privateなasyncメソッド
const Hoge = (() => {
    const wm = new WeakMap()
    const privates = function(instance) {
        return wm.get(instance) || wm.set(instance, {}).get(instance)
    }

    return class Hoge {
        constructor() {
            privates(this).name = 'Hoge class'

            privates(this).asyncMethod = function() {
                return async function() {
                    return privates(this).name
                }.bind(this)()
            }.bind(this)

        }

        callPrivateAsyncMethod() {
            return privates(this).asyncMethod()
        }
    }
})()

var hoge = new Hoge
console.log(hoge.callPrivateAsyncMethod())
// Promise {<resolved>: "Hoge class"}
```

WeakMapにsetしたオブジェクトに、thisをbind()したasync関数を即時実行した結果を返すthisをbind()した関数を格納する(何言ってるのか分らない)

# 最後に
js初心者です。
間違っていることや足りないことがあれば教えていただけると嬉しいです

WeakMapを使う方法は可読性の低下とデバッグのしにくさ手間がかかるなど問題点はあるががしっかりprivateになっている
ただただめんどくさそう。