ちょっと高度にJavaScript/クロージャの基礎
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」とあったら、そこはクロージャ使ってるかもしれません。
今回の例では、カウンターのサンプルを用いてクロージャとは何かを解説しました。しかし実際にこのカウンターが何かの役に立つかというと、微妙なところだと思います。次回は、もっと具体的な用例でクロージャの使い方を解説します。