JavaScriptでprivate「Symbol」編

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

コンストラクタの闇↓
[JavaScriptのコンストラクタとかいう闇深いものとprivate](https://qiita.com/schrosis/items/328eac06e7e28a9572dc)
WeakMap編↓
[JavaScriptでprivate「WeakMap」編](https://qiita.com/schrosis/items/76c4c10461584f4b5285)
## Symbolとは
>Symbol() 関数は常に一意の値を返します。symbol 値はオブジェクトのプロパティ識別子として使われます。symbol 型はこの目的のためだけに使われます。
[Symbol](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol)

よく分からないですが一意な値を生成してくれるみたいですね
なにかしらのトークン作るときとか便利そう！よしどんな値が返ってくるのか見てみよう！

```js
console.log(Symbol())
// Symbol()

console.log(Symbol().toString())
// Symbol()

console.log(String(Symbol()))
// Symbol()
```

>Symbol() 関数は、symbol 型の値を返します。
オブジェクトのプロパティ識別子として使われます。
symbol 型はこの目的のためだけに使われます。
[Symbol](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol)

一意な文字列作ってくれる関数ではないですね
**どんな値が入っているのかは見れないです**

しかしちゃんと一意な値を返してくれています

```js
const sym = Symbol()

console.log(sym === Symbol())
// false
```

ちなみにSymbol関数の第一引数を使ってSymbolにデバッグ用の説明文を付けることができます

```js
const nameSymbol = Symbol('name')

console.log(nameSymbol)
// Symbol(name)
```

一意になることは分ったので次
オブジェクトのプロパティ名(keyの方)にSymbolを使ってみます

```js
const hoge = Symbol('hoge')
var obj = {}

obj[hoge] = 'hoge'

console.log(obj)
// {Symbol(hoge): "hoge"}
console.log(obj[hoge])
// hoge
```

Symbolをキーにプロパティを作ることができました
ポイントはSymbolをキーにしたプロパティへアクセスするには**同じSymbolが必要**だということです


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

privateにするプロパティの名前をSymbolを使って定義します

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

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

**手順3**
Symbolを使ってクラスを定義する

```js:クラス構文

const Hoge = (() => {
    const privateProperty = Symbol()
    const privateMethod = Symbol()

    return class Hoge {
        constructor() {
            this[privateProperty] = 'プライベート'
        }

        publicMethod() {
            return this[privateMethod]()
        }

        [privateMethod]() {
            return this[privateProperty]
        }
    }
})()

var hoge = new Hoge
console.log(hoge)
// Hoge {Symbol(): "プライベート"}
//  Symbol(): "プライベート"
//  __proto__:
//   constructor: class Hoge
//   publicMethod: ƒ publicMethod()
//   Symbol(): ƒ [privateMethod]()
//   __proto__: Object

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

```js:関数構文の場合
const Hoge = (() => {
    const privateProperty = Symbol()
    const privateMethod = Symbol()

    function Hoge() {
        this[privateProperty] = 'プライベート'
    }

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

    Hoge.prototype[privateMethod] = function() {
        return this[privateProperty]
    }

    return Hoge
})()

```

Symbolの数が増えると変数名が衝突する可能性が増える
なので下記のようにすると良い

```js
const Hoge = (() => {
    const privateSymbols = {
        privateProperty : Symbol(),
        privateMethod   : Symbol()
    }

    return class Hoge {
        constructor() {
            this[privateSymbols.privateProperty] = 'プライベート'
        }

        publicMethod() {
            return this[privateSymbols.privateMethod]()
        }

        [privateSymbols.privateMethod]() {
            return this[privateSymbols.privateProperty]
        }
    }
})()

```

`privateSymbols`オブジェクトにSymbolを入れて衝突を考慮する
※`privateSymbols`オブジェクトの名前は`_`でもなんでもいい


## 継承

アロー関数の結果でクラスが返ってくるので継承することも可能

```js
const Parent = (() => {
    const privateSymbols = {
        privateProperty : Symbol(),
        privateMethod   : Symbol()
    }

    return class Parent {
        constructor() {
            this[privateSymbols.privateProperty] = 'プライベート'
        }

        [privateSymbols.privateMethod]() {
            return this[privateSymbols.privateProperty]
        }
    }
})()

class Child extends Parent {}

console.log(new Child)
// Child {Symbol(): "プライベート"}
//  Symbol(): "プライベート"
//  __proto__: Parent
//   constructor: class Child
//   __proto__:
//    constructor: class Parent
//    Symbol(): ƒ [privateSymbols.privateMethod]()
//    __proto__: Object
```

## 良い点
 - アロー関数は即時実行で一回しか実行されない
  - なので複数回インスタンス化しても重くない
 - コンソールでSymbolプロパティの値を確認できる
  - Symbolに説明を付けておくと開発するときに便利

## 問題点
 - クラスを定義する前にprivateなプロパティ名を列挙しなければならない
  - どうにかできる方法があれば教えてください！
 - publicとprivateで書き方がだいぶ違う
 - クラス作るたびにアロー関数作ってシンボル作ってとやることが多い
 - 完全な**privateじゃない**

## 外からアクセスできる
一意な値を生成するSymbolを使っているので、外からは同じSymbolを作ることはできず、アクセスできないように見える

しかし`Object.getOwnPropertySymbols()`というSymbolプロパティを取得するメソッドがある
[Object.getOwnPropertySymbols()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols)


```js
function getSymbols(obj) {
    let symbols = []
    let o = obj
    while (o) {
        symbols = symbols.concat(Object.getOwnPropertySymbols(o))
        o = Object.getPrototypeOf(o)
    }

    return symbols
}
```
なんて関数用意しておけば簡単にSymbolを取得できてしまう

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

Symbolを使う方法は可読性と手間の割りに実は外からアクセスできてしまったりちょっと惜しいなぁって思います