注目の記事

ちょっと高度にJavaScript/クロージャの基礎

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

JavaScriptのクロージャを一言で言うと、「自身が定義されたスコープにおいて変数を解決する関数」となります。
少し複雑ですが、マスターすれば何かと便利な概念なので、解説したいと思います。
ちなみに、JavaScript未経験なFlex開発者にオススメしたいClosure ToolsのClosure Toolsとは別物です。まぎらわしくてすみません。

var f = function() {
	var i = 0;
	return function() {
		return i++;
	}
}();
console.log(f());// 0を出力
console.log(f());// 1を出力
console.log(f());// 2を出力

変数iは、匿名関数のローカルスコープに定義されています。ローカルスコープの変数は、関数の処理が終わると無くなってしまうのが普通ですが、関数fの出力結果を見る限りは、なぜか保持されているのが確認できます。この関数fと、保持されている外側の関数(エンクロージャ)のローカル変数をひっくるめてクロージャと呼びます。

読み解く

では、一体何が起きているのかを読み解いていきましょう。
上の例は、匿名関数を用いて意図的に短く書いてあるので、何をしているのか読みにくいかもしれません。少し読みやすくしてみます。

function myEnclosure() {
	var i = 0;
	function myClosure() {
		return i++;
	}
	return myClosure;
}
var f = myEnclosure();
console.log(f());// 0を出力
console.log(f());// 1を出力
console.log(f());// 2を出力

関数myEnclosureを実行すると、そのローカルスコープに変数iと関数myClosureが生成され、myClosureは戻り値として返されます。実はこの時、戻り値のmyClosureには「自身が定義されたスコープ」つまりmyEnclosureのローカルスコープ変数も付随しているのです。

この時、変数(かつ関数)fに代入されたmyClosureから見た変数iは、ローカル変数ではありません。よって、関数fの実行が終了しても、無くなることはありません。かと言って、この変数iはグローバル変数でもありません。関数fの内部からしかアクセスできないこの変数を、「レキシカル変数」と呼びます。

応用

では、以下の例では、どのような結果になるでしょうか?

function myEnclosure() {
	var i = 0;
	function myClosure() {
		return i++;
	}
	return myClosure;
}
var f = myEnclosure();
console.log(f());// 0を出力
console.log(f());// 1を出力
console.log(f());// 2を出力

var g = myEnclosure();
console.log(g());// ?
console.log(g());// ?
console.log(g());// ?

この場合、関数fと関数gが持つレキシカル変数は、定義されている場所は同じであっても実体は別々となります。関数myEnclosureが実行されるたびに、クロージャが生まれます。結果として、関数gの実行でも、再度0,1,2が出力されます。

まとめ

誰かの書いたスクリプトに「return function」とあったら、そこはクロージャ使ってるかもしれません。

今回の例では、カウンターのサンプルを用いてクロージャとは何かを解説しました。しかし実際にこのカウンターが何かの役に立つかというと、微妙なところだと思います。次回は、もっと具体的な用例でクロージャの使い方を解説します。