クロージャを使ってプライベートな変数を設ける

ここではオブジェクトに対してプライベートな変数を設ける方法と、クラスに対してプライベートな変数を設ける方法を紹介します。JavaScript第5版のP.197-198を見て少し勉強しました。javascriptのプライベート変数というと、varというキーワードを思い浮かべるかもしれません。しかしvarキーワードで宣言したプロパティの値は、varキーワードを囲っている関数の実行が終わった後は破棄されてしまいます。だからといって、varキーワードが無ければ、グローバルオブジェクトとなり、外部から誰でも参照できるようになってしまいます。特定のプロパティを外部から参照されたくない、なおかつ値も保持しておきたい。そんな場合に以下のようなテクニックが必要となります。

JavaScript 第5版

JavaScript 第5版

オブジェクトに対するプライベートな変数

var obj = {};
(function(){
	var x = 40;
	function getX(){
		return x;
	}
	obj.getX = getX;
})();

function init(){
	alert(obj.getX());
}

この例はオブジェクトobjに対して、外部からアクセスできないプライベートな変数xと、外部からアクセス可能なgetXというメソッドを設けるためのコードです。順番にみていきましょう。
二行目からは、(function(){})()という構造になっています。これはこの内部で関数を定義し、それをすぐ呼び出すというものです。よって、このコードが呼び出されたとき、すぐにfunctionの内側が実行されるというわけです。さらに、functionの内部を見てみましょう。

var x = 40;
function getX(){
	return x;
}
ns.getX = getX;

まず、内側の一行目でvar xが定義されています。これは関数内のローカル変数なので、外部からは参照されません。ここまでは良いですね。次にfunction getX()を定義、こちらも外部からは参照されません。ここがポイントなんですが、このgetXはfunction(){}の入れ子で定義されています。そしてgetX内ではxを参照しています。そして、最後に外側で定義しているnsオブジェクトに、getXというプロパティを追加し、先ほど定義したfunction getX()を代入します。

通常var xは二行目のfunction()を実行中にのみ有効な変数です。一度実行が終わってしまえば、破棄されてしまいます。しかし、入れ子関数getXによって値を参照しています。この入れ子関数getXというのも、xと同様に二行目のfunction()を実行中にのみ有効なもので、function()の実行が終わってしまった後は利用することができません。しかし、ここで外部のオブジェクトnsから参照を行うことによって、getXは保持されます。また、getXはxを参照していますので、同様にxの値も保持されるというわけです。

そして最後にinitを定義しています。ここでは、nsのgetXプロパティを通じて、先ほど内部で定義したgetX()を呼んでいます。

function init(){
	alert(ns.getX());
}

このとき外側からの名前空間からは、nsとinitしか見えません。つまり直接var xにアクセスする方法がありません。var xの値を取得したい場合は、function getX()というアクセサを使って取ってくる必要があります。

クラスに対するプライベートな変数

オブジェクトに対してプライベートな変数を設ける場合よりも、クラスに対してプライベートな変数を設ける方が記述的にはすっきりとした構造になります。ここでは、Fooというクラスに対して、プライベートなプロパティx, yとグローバルなメソッドoutputとsetXを設けます。最初にダメな例をご覧ください。

function init(){
	foo = new Foo(2,3);
	alert( foo.output() );

	foo.setX(4);
	alert( foo.output() );

        alert(foo.x); //外部からアクセスできてしまう	
}

var Foo = function(x,y){
	this.x = x;
	this.y = y;
	
	this.output = function(){
		return this.x*this.y;
	};
	
	this.setX = function(x){
		this.x = x;
	};
};

これは通常のクラスの使い方です。init関数で最初にFooからインスタンスを生成しています。Fooの内部を見てみると、引数としてもらった値をそれぞれthis.x、this.yとして登録しています。今回このx, yを外部からアクセスできないようにしたいという話だったのですが、このようにthisキーワードを使ってしまうと、外部からアクセスできる状態になってしまいます。outputとsetXは、もともと外部に公開するという話だったので、これで問題ありません。

initに戻りましょう。3行目をご覧ください。initでは最初に生成したインスタンスから、ouputというメソッドを呼んでいます。outputメソッドの内部ではxとyの値を利用し、x*yという計算を行っています。最初はxが2、yが3ですので、2*3で6が返ってきます。

次にfoo.setX(4)ということで、fooオブジェクトのxというプロパティの値を4に書きかえています。そして、またまたoutputメソッドを呼びます。今度はxが4でyが3ですので、4*3で12が返ってくることになります。

最後にfoo.xとすることで、fooの持つxプロパティに外部から直接アクセスできてしまうことを示しています。ここでは4という値が返ってきます。これをどうにかして防ぎたいです。

次に、クロージャのテクニックを使ってxとyに外部からアクセスできないようにしてみましょう。
function init(){
	foo = new Foo(2,3);
	alert( foo.output() );

	foo.setX(4);
	alert( foo.output() );

        alert(foo.x); //undefinedが返ってくる
}

var Foo = function(x,y){
	this.output = function(){
		return x*y;
	};
	
	this.setX = function(_x){
		x = _x;
	};
};

init関数については先ほどと同じものです。Fooについてみていきましょう。Fooは最初にxとyという値を受けとっています。ここで受け取ったxとyはvar x、var yと同じものです。要するにFoo関数が実行中にのみ有効な値となります。outputとsetXの右辺を見てみると、ここで定義したxとyを利用して何らかの処理を行っています。つまり、入れ子関数がxとyを参照しているという状態になります。この入れ子関数もFoo関数を実行中にのみ有効な値です。しかし、これをthis.outputとthis.setXに代入することによって、外部からアクセスできるようになります。これによって、xとyの値はFoo関数の実行が終わったあとでも保持されることになります。
さて次にoutputの内部を見てみると、x*yを返しています。最初の例ではthis.xとthis.yを返していましたが、こちらではx*yを返します。次にsetXの内部を見てみます。ここで特徴的なのが引数を_xとしているところです。もしこれをxとしたら、先に定義していたvar xとぶつかることになります。そのためあえて名前を変えているというわけです。
xとyはvarとして定義されていますので、外部からアクセスすることはできません。undefinedが返ってきます。