【すっきり】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 が起きる。