注意: 本記事は
Symbol
などの画期的な機能が存在しないIEなどの邪悪な古いブラウザを対象としたものです。「IEなんてアウト・オブ・眼中!オレは時代の最先端を征く!」という方はブラウザバックするか、ページ最後の「おまけ」にある、class
式のプライベートフィールドやSymbol
を使った例を参照してください。
普通のクラスの場合、代入値は簡単に変更できてしまいます。
/* カウンター用のクラス */
function X() {
this.num = 0;
}
X.prototype.add = function(v) {
this.num++;
}
var x = new X;
x.add();
console.log(x.num);
// 1
/* カウントの値を不正なものに書き換える */
x.num = 'aaa';
console.log(x.num);
// 'aaa'
x.add();
console.log(x.num);
// NaN
本来想定している値とは違うものに変えられてしまうと、予期せぬエラーの原因になってしまいますね。
#解決策
-
Object.defineProperties
などを使ってゲッターを設定し、代入値ではなくクラス内の変数を返却させる。 - クラス内の変数にアクセスするため、メソッドは
prototype
への追加ではなく直接this
で代入する。 - メソッドを書き換えられないよう、
this
をロックする。
function X() {
var _num = 0;
Object.defineProperties(this, {
'num': {
get: function() {
return _num;
}
}
})
this.add = function() {
_num++;
}
Object.freeze(this);
}
var x = new X;
x.add();
console.log(x.num);
// 1
/* カウントの値の書き換えは無効 */
x.num = 'aaa';
console.log(x.num);
// 1
/* Object.freezeによって、メソッド・代入値の書き換えもロック */
x.add = function(){};
console.log(x.add);
// function() {
// _num++;
// }
x.b = 1;
console.log(x.b);
// undefined
最後に
自分で書いておいてそれはどうかとは思いますが、この手法は使うべきではないでしょう。
メソッドがprototype
に登録されていなかったり、代入値の新規作成・変更が不可だったり(これについてはthis
をロックしなければいいだけですが)と、普通の仕様とは全くの別物であるため、面倒な制約も数えきれないくらい付いてくることになるでしょう。
この手法はそんなに複雑なものではないですし、同じようなものを考え付く方は普通にいらっしゃると思います。しかしネットで検索してもこのようなものが出てこないということは、つまるところ使うメリットが無いということなんだと私は思います。「どうしても、そう、どうしても、書き換えられたくないんだ!」という人以外は、書き換えられるリスクを飲み込んで普通のクラスを書くことをお勧めします。
おまけ
class
式をサポートしているブラウザのうち、その一部のブラウザには、「プライベートフィールド」なるものがあります(Safariは未サポート、Firefoxは試験的機能をオンにしている場合のみ。Babelに通してご利用ください。)。これを使えば、上と同様のことをデメリット無しに行えます。
class X {
#num;
constructor() {
this.#num = 0;
}
add() {
this.#num++;
}
get num() {
return this.#num;
}
}
var x = new X;
x.add();
console.log(x.num);
// 1
console.log(x.#num);
// Uncaught SyntaxError: Private field '#num' must be declared in an enclosing class
あるいはSymbol
を使うという手もあります。
(global=>{
var num = Symbol('num');
var X = class {
constructor() {
this[num] = 0;
}
add() {
this[num]++;
}
get num() {
return this[num];
}
}
global.X = X;
})(this);
丸ごと無名関数などに放り込み、クラスのみグローバルに放り出せば、外部からのシンボルへのアクセス方法が絶たれることで、実質的に代入値がプライベート化されます。何よりこちらはES6で規定されたものなので、モダンブラウザ全てで対応しています。