JavaScriptでクロージャ

JavaScriptはクロージャが使える言語です。クロージャと聞くとなかなかピンときにくい概念なんですが、ものすごく端折って言ってしまうと、関数(メソッド)の中から、その外側で定義された変数にアクセスできるというものです。


var adder = function() {
	var value = 0;
	return {
		add: function(inc) {
			value += inc;
		},
		getValue: function() {
			return value;
		}
	}
}

var a = adder();
a.add(2);
a.add(5);
console.log(a.getValue());	// 7と出力される。

外側の関数で宣言したvalueという変数に、内部で生成して返すオブジェクトのメソッドからアクセスしています。

valueは普通はadder()の呼び出しが完了した時点で消えてしまうはずですが、内部で生成したオブジェクトから参照されているため、消えずに残っているのです。

これで何が便利になったかと言うと、valueという、実装内部でしか使わない変数のカプセル化が実現できるということです。

adder()の呼び出しで返されるオブジェクトからは、 valueの存在は隠蔽されてしまっており、外側からアクセスする手段が無いのです。

JavaScriptはオブジェクト指向をサポートする言語ですが、普通にオブジェクトを生成しただけではカプセル化を実現できません。


var Adder = function() {
	this.value = 0;
}
Adder.prototype.add = function(inc) {
	this.value += inc;
}
Adder.prototype.getValue = function() {
	return this.value;
}

var a = new Adder();
a.add(2);
a.add(5);
console.log(a.getValue());	// 7と出力される。

ここでは、コンストラクタ呼び出しとプロトタイプ継承を使って最初の例と似たようなオブジェクトを生成しています。

ここまでは、最初の例と同じような使い方ができていますが、valueという変数は実はカプセル化されていません。そのために、


a.value = 10;
console.log(a.getValue());	// 10と出力される。

というふうに、直接valueを書き換えることが出来てしまいます。他の言語にあるような、privateとか、protectedといったようなアクセス指定子もありません。

そこで最初の例でみたように、カプセル化を実現するためにクロージャを使うパターンがよく見受けられます。(寧ろクロージャの方が推奨されている?)

 

クロージャの別の使用例として、メモ化という手法があります。例えば、ある引数に対して時間のかかる計算をして結果を返す関数があったとします。


var complex = function(n) {
	// 時間のかかる計算
	result = ...
	return result;
}

一度計算した結果は保存しておいて、計算済みの引数に対しては保存済みの結果を返すことで、何度も計算せずに済みます。


var complex = function() {
	var memo = [];
	var comp = function(n) {
		var result = memo[n];		// 計算済みの結果があればそれを使う
		if (typeof memo[n] === 'undefined') {
			// 時間のかかる計算
			result = ...
			memo[n] = result;		// 計算した結果を保存しておく
		}
		return result;
	};
	return comp;
}();

var a = complex(10);
var b = complex(20);
var c = complex(10);	// 最初の引数と同じなので計算済みの結果が使われる。

ここでは、クロージャを使って、memoという配列に計算結果を保存するようにしています。

無名関数を定義するとともに、最後に即座に実行(最後の()で呼び出しいる)して、内部で定義したcompという関数オブジェクトを得ています。このcompからmemoの存在は隠蔽されています。

クロージャを使う場合、このように関数の呼び出しで、内部で生成したオブジェクトを返すというのが典型的な書き方ですね。

もちろん、アクセス指定子を備えたクラスをサポートする言語であれば、クラスを使っても同じようにできますが、クロージャであれば手軽にこういった実装が出来るところが便利なところです。クラスを書いてインスタンスを生成して…とやるより、コード量も少なくてすみます。

 

慣れるまでは頭がこんがらがりそうですが、クロージャを使いこなせるようになると、一端のJavaScript使いと言えるかもしれませんね。

 

 

あまり言語の経験が無い人はこちらでじっくり

 

他の言語の経験が十分にある人は、こちらで

言語として欠点も備えたJavaScriptですが、JavaScriptで学ぶべき良い部分をピックアップして百数十ページにコンパクトにまとめてあります。