◆ クロージャの特徴
(1) クロージャは変数に代入可能であり(つまり値であり)、
それを使用して関数の呼び出しができる。
(2) クロージャはそれが生成された箇所のローカル変数を参照できる。
具体的に上記を説明する前にサンプルコードを示す。
1: <script> 2: var outer=function() { 3: this.x = 1; 4: this.inner= function() { // closure 5: this.x = this.x + 1; 6: }; 7: } 8: var o = new outer(); 9: // outer関数から変数xを参照 10: alert(o.x); // 1 11: // innter関数を呼び出し 12: o.inner(); 13: // outer関数から変数xを参照 14: alert(o.x); // 2 15: // innter関数を呼び出し 16: o.inner(); 17: // outer関数から変数xを参照 18: alert(o.x); // 3 19: </script>
まずは特徴(1)から。
innerという変数に代入し(4行目)、その後、その変数経由で関数を呼び出している(12、16行目)。
ただ、これを実現するだけなら、Cであれば関数ポインタを使えばいい。
しかし(2)はできない。
サンプルコード内で変数xが複数の関数呼び出しをまたがり保持されている。
ここがクロージャをクロージャ足らしめている点である。
まとめると・・・
関数ポインタのような使い方ができるが、クロージャが生成された
箇所の変数も保持できる、これがクロージャである。
◆ 実装方法
どのように実装されるのかを説明する。
ローカル変数の領域は、関数に入った時点でスタック上に確保され、
抜けるときに解放されるのだが、この確保/解放されるひとかたまりのメモリをフレームと呼ぶ。
関数が呼び出された時に、その中で定義された変数と値を格納しておく場所である。
さて、サンプルコードに戻っていただきたい。
どこでフレームが作られるか。
outer内で1つのフレーム、そして
inner内でもう1つのフレーム、の合計2つである。
今回はinner内で新しい変数は出していないが、ここでyなどを定義すれば、
inner内のフレームに保持されるわけだ。
クロージャの特徴として、(2)があった。
再掲する。
"クロージャはそれが生成された箇所のローカル変数を参照できる。"
つまり、クロージャはouterとinnerの複数のフレームを保持しなければならない。
ではどうやって複数のフレームを保持するのか。
スコープチェーンを使えばいい。フレームを連結して管理するためのものだ。
◆ 処理フロー
サンプルコードを使い、処理フローを追いかける。
(a) 普通の関数outerを呼び出す(8行目)。
スコープチェーンが生成され、その中でフレームも作られる。
(b) outerが呼び出されたことで、その関数内でクロージャinnerが生成される(4行目)。
クロージャはそれが生成された時点での、つまり(a)のスコープチェーンを参照し保持する。
(c) innerが呼び出されると(12行目)、ここでは普通の関数の呼び出しと同じように
新しくスコープチェーンが生成され、その中でフレームが作られる。
だがしかし、新規に作ったスコープチェーンに(b)で参照し保持しているスコープチェーンも連結させる。
あとは繰り返し。
このように(a)、(b)、(c)の3つの規則でクロージャは実装できるわけだ。