2010年7月23日金曜日

closureとは何か

closureとは何か。javascriptを使って説明する。


◆ クロージャの特徴
(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つの規則でクロージャは実装できるわけだ。