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