クロージャの説明とActivation Objectについて
JavaScriptにおけるクロージャの説明記事を見ると、スコープチェーンや高階関数には触れているのにActivation Object(Call Object)の説明が省かれていることが多いような気がする。
「入れ子関数を呼び出すことで保持されている変数を参照することができます」
ん?呼び出し終わったのにどこに保持されてるの?
staticな領域に格納されて参照できるようになるってこと?
「別変数としてもう一つカウンタを作ってみると、それぞれ独立した値を持つカウンタが~」
えっ同じ変数参照されるんじゃないの?
staticじゃないの?
たぶん僕の理解力が低いのと、カッチカチのJava脳のため躓いてるんだと思うんですが、
ある書籍にてActivation Objectの説明を見た時にすんなり理解できたのでメモしておきます。
Activation Object
Activation Object(以下AO)というのは、"定義された変数や関数の管理を行う目的を持った、関数が呼び出される度に内部で生成されている便宜的なオブジェクト"のことで、Call Objectとも言われます。「よくわからんけど、内部で変数管理してるんでしょ」という感じだけど、重要なのは「関数が呼び出される度に生成される」という部分。
クロージャの説明で多用されるカウンタを例にとってみます。
function counter(init) { var num = init; return function() { return ++num; }; }; var c1 = counter(10); c1(); // ->11 c1(); // ->12 var c2 = counter(100); c2(); // ->101 c2(); // ->102
numはAOに格納されている
counter関数の呼び出しを行うと内部的に"入れ子関数内の管理を行うAO" と "counter関数内の管理を行うAO"が生成され、変数numは後者のAOに保持されることになります。入れ子関数内ではスコープチェーンによってnumが参照され、インクリメントされた後に返されます。
(スコープチェーンって、Global Object(グローバルスコープに定義された変数や関数を管理する便宜的なオブジェクト)とAOが生成された順に連結されたものを意味するらしいので、Activation Objectチェーンと呼んだ方がしっくりくるのでは)
AOは関数が呼び出される度に内部で生成され、参照が生きている間は破棄されない
counter関数を再度呼び出して別変数に代入してみると、それぞれが独立した挙動をすることが確認できます。
これは変数c1に代入された関数に紐付くAOと、c2に代入された関数に紐付くAOが別物であるということを意味し、かつ入れ子関数の呼び出しによってそれぞれのAOを参照できている状態にあるためです。
AOへの参照が生きている限りAOは破棄されないため、変数numは保持され続けることになります。
まとめ
JavaScriptのクロージャが理解されにくいのは、クロージャをなにかデザインパターンのような特殊なものと位置づけてしまっているからで、「関数に穴をあけてActivation Objectを参照し続けるようにすることで、擬似的なプライベート空間として使うことができる。クロージャはそのための手法にすぎない」と軽く捉えてしまえば、モジュールパターンや実際の活用場面についても難なく入っていくことができるのでは、と思います。