【すっきり】JavaScriptのクロージャのしくみ。
2016/6/5
要点。
JavaScriptの関数は変数に代入できる。
var asFunc = function () { // 関数を定義して代入。 var data = 0; ... }
そして実行時に生成されたローカル変数も一緒に変数に代入できる。
var asClosure = asFunc(); // 関数を実行し、生成されたローカル変数もろとも代入。
これがクロージャである。
関数 | CODE |
---|---|
クロージャ | CODE + DATA |
サンプル。
もっともこれではDATAの存在を確かめようがないのでサンプルを作ってみる。
var asFunc = function f1() { var data = 0; return function f2() { return ++data; }; }; // 1. func1を関数として毎回新規に実行。 alert(asFunc()); // returns f2 (f1実行 → DATA生成 → DATA破棄) alert(asFunc()()); // returns 1 (f1実行 → DATA生成 → f2実行 → DATA破棄) alert(asFunc()()); // returns 1 (f1実行 → DATA生成 → f2実行 → DATA破棄) alert(asFunc()()); // returns 1 (f1実行 → DATA生成 → f2実行 → DATA破棄) // 2. func1をクロージャ化してDATAを保持しつつ実行。 var asClosure = asFunc(); // returns f2 (f1実行 → DATA生成) alert(asClosure); alert(asClosure()); // returns 1 (f2実行) alert(asClosure()); // returns 2 (f2実行) alert(asClosure()); // returns 3 (f2実行)
f1が返すf2によって、f1のローカル変数にアクセスできる。 よく見るサンプルだがこのような使い方は実用的じゃない。トリッキーでわかりにくい。
本来の使われ方。
では何のためのクロージャなのか? 非同期処理でコールバックを書くときにこれほど便利なものはない。
function apiCall() { var url = "..."; httpRequest(url, // 何かの非同期処理。 function onComplete(status, body) { // コールバック。 parse(url, status, body); // ここからurlにアクセス可。 ... } ); }
apiCall()を抜けた後に、onComplete()がコールバックされても、urlにアクセス可能。 便利な一方で、どこか気持ち悪い。腑に落ちないものがあった。
気持ち悪さの理由。
それは、C言語などの感覚では一旦apiCall()を抜けたらauto(=stack)変数のurlは存在しないから。 コンパイラやインタープリタの仕組みを知っていて、中の動きを想像しながらコードを書くならなおさらだ。
しかしJavaScriptではこれが許される。それはJavaScriptではauto=heapだからだ。 ここがわかれば全ての謎が氷解する。
ついでに Objective-C の blocks
同じようにObjective-Cのblocksも便利である。ただしC言語なので基本はauto=stack。コールバック内から親の変数にアクセスする場合は __blocks 属性を付けてheapに割り当てる必要がある。
blocksを呼ぶ側にも注意が必要。blocksポインタはstackにほったらかしにせず、heapやinstanceにコピーしてstaticに保持する。そうしないとblocks自体が解放されてしまって、後から呼び出したときに EXC_BAD_ACCESS が起きる。