8
6

More than 5 years have passed since last update.

JavaScriptでprivate「Symbol」編

Last updated at Posted at 2019-02-19

JavaScriptでprivate「Symbol」編

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

コンストラクタの闇↓
JavaScriptのコンストラクタとかいう闇深いものとprivate
WeakMap編↓
JavaScriptでprivate「WeakMap」編

Symbolとは

Symbol() 関数は常に一意の値を返します。symbol 値はオブジェクトのプロパティ識別子として使われます。symbol 型はこの目的のためだけに使われます。
Symbol

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

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

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

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

Symbol() 関数は、symbol 型の値を返します。
オブジェクトのプロパティ識別子として使われます。
symbol 型はこの目的のためだけに使われます。
Symbol

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

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

const sym = Symbol()

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

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

const nameSymbol = Symbol('name')

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

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

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を使ってクラスを定義する

クラス構文

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())
// プライベート
関数構文の場合
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の数が増えると変数名が衝突する可能性が増える
なので下記のようにすると良い

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オブジェクトの名前は_でもなんでもいい

継承

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

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()

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

8
6
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6