というわけでJavaScirptのクラス内にprivateメソッドやらprivateプロパティやらを実現したいな、と思って調べた時に色々知ることがあったのでまとめてみる。
主な実装方法
調べてみると4つほど方法がありました。
- privateメソッド・プロパティの接頭辞に_を付ける
- クロージャを使った実装
- Symbolを使う
- WeakMapを使う
それぞれについてうんうんと考えてみましょう。
privateメソッド・プロパティの接頭辞に_を付ける
これは超簡単ですね。
以下の様な感じ。
var Hoge = function(){};
Hoge.prototype = {
publicMethod: function() { console.log('パブリックメソッドだよ!') },
_privateMethod: function() { console.log('プライベートメソッドだよ!') }
}
var hoge = new Hoge();
hoge.publicMethod();
hoge._privateMethod(); // プライベートメソッドなのに呼べちゃう
これはただの紳士協定で、「プライベートメソッドは接頭辞にアンダースコアを付けるので外から呼ばないでね」って感じのものです。
ゆるい。
クロージャを使った実装
これはこちらの記事にとても詳しく書いてあります。 (@jun1s様に感謝)
メモリ消費の観点から現実的では無い模様。
Symbolを使う
SymbolはES6で新たに追加されたプリミティブなデータ型ですね。
これを使って果たしてprivateを実現できるのでしょうか。
とりあえずやってみました。
class Hoge {
constructor() {
let privateProperty = Symbol['privateProperty']
let privateMethod = Symbol['privateMethod']
}
setFoo() {
this[this.privateProperty] = 'foo'
}
getFoo() {
return this[this.privateProperty]
}
setBar() {
this[this.privateMethod] = function() {
return 'bar'
}
}
getBar() {
return this[this.privateMethod]()
}
}
let hoge = new Hoge()
hoge.setFoo()
console.log(hoge.getFoo())
hoge.setBar()
console.log(hoge.getBar())
これでHogeクラスにprivateプロパティ・メソッドが実現できました。
ただし、Symbolを使った方法は1点問題があり、
- そもそもprivateじゃない
という問題点があります。 どういうことでしょうか?
そもそもprivateじゃない問題
実は this[this.sym] で設定した値を外から取得する方法があります。
それは getOwnPropertySymbols を使うとSymbolが取得できてしまうというもの。
ただ、手元でやってみた結果Symbolが取得できませんでした・・・
何故に。
class Hoge {
constructor() {
let sym = Symbol['privateMethod']
}
setFoo() {
this[this.sym] = 'foo'
}
getFoo() {
return this[this.sym]
}
}
let hoge = new Hoge()
hoge.setFoo()
console.log(Object.getOwnPropertySymbols(hoge))
// 結果
// []
参考:
というわけでSymbolを用いた方法もちょっと違うかなと。
[追記]
@gaogao_9さんからアドバイスを頂けました。
@syossan27
— がお (@gaogao_9) August 9, 2016
const hogeSymbol = Symbol("hoge");
class {
[hogeSymbol](){
/* 処理 */
}
}
みたいな動的プロパティ指定が出来ますのでこれでprivateメソッドを簡潔に書けますよ。
ご教授頂いたコードがこちら。
const Hoge = (()=> {
const privateProperty = Symbol('privateProperty')
const privateMethod = Symbol('privateMethod')
class Hoge {
constructor() {
}
set foo(value){
this[privateProperty] = value
}
get foo(){
return this[privateProperty]
}
[privateMethod](){
return 'bar'
}
get bar() {
return this[privateMethod]
}
}
return Hoge;
})();
let hoge = new Hoge()
hoge.foo = 'foo'
console.log(hoge.foo)
console.log(hoge.bar())
こちらの方が見やすいですね!
WeakMapを使う
WeakMapというMapのキーにオブジェクトが使える方法があるので、それを使ってprivateを実現する。
const privateMap = new WeakMap()
function getPrivates(self) {
let p = privateMap.get(self);
if (!p) {
p = {};
privateMap.set(self, p);
}
return p;
}
class Hoge {
constructor() {
}
setFoo() {
getPrivates(this).foo = 'foo'
}
getFoo() {
return getPrivates(this).foo
}
setBar() {
getPrivates(this).bar = function() {
return 'bar'
}
}
getBar() {
return getPrivates(this).bar()
}
}
let hoge = new Hoge()
hoge.setFoo()
console.log(hoge.getFoo())
hoge.setBar()
console.log(hoge.getBar())
WeakMapでclassをセットして、そこにprivateプロパティなりメソッドなりをセットするような感じ。
getPrivates(hoge).foo とかで取れちゃうじゃん!って思われるかもしれませんが、実際にはclass部分を export default class とかで切り出して、
実行部は別にするので大丈夫。
まとめ
なんとなーく薄い理解ですが、WeakMapを使えばprivateが実現できますが深いことを考えなければSymbolでもいいのかなと。
更に深いことを考えなければアンダースコア使っちゃってもいいのかなと。
この辺りは実装者によってだいぶ分かれそうですね。
なんかふんわり理解をまとめた感じなので、「これ違うだろ」というツッコミ頂けると喜びます。