結論:
クロージャは、ClosureというよりEnclosureである。
クロージャとは、あるスコープに同封された別のスコープのことである。
クロージャとは、そういう同封を使った関数のことである。
クロージャとは、そうやって同封された変数のことである。
他の言語にも言える内容かもしれませんが、今回はJavascriptの話です。
「クロージャ」でぐぐると、いろんな曖昧な説明が出てきてどうもはっきりしません。クロージャという言葉が具体的に何を指すのかをはっきり理解しておきたいです。
まず多いのは、「こういう形式の関数がクロージャです」という説明。
var foo = function(){
var count = 0;
return function(){
return count++;
};
}; // foo()を実行すると、内側の関数が返ってくる
// 返って来た関数を実行すると、countがひとつずつ増える
えっ、どれ?
関数を返す関数がクロージャですか?それとも関数から返されるのがクロージャ?それとも外部スコープの変数を使う関数がクロージャ?
他にも、「関数がreturnしたあとにも生存するローカル変数」とか、「関数定義時の環境を参照できるようなデータ構造」とかいろんな説明が見つかる。なんとWikipediaを見てみると、「(略)な関数のことである」「関数とそれを評価する環境のペアである」「環境に紐付けられたデータ構造のことを言う場合もある」と書いてあります。えらいこっちゃで。
closureを辞書で引かずに浮気する
closureを辞書で引いても「閉じていること」「閉包」「最終的な心の整理」などと言われてイマイチよく分かりません。そこでclosureはenclosureの略だということにして、そっちを調べてみました。
- Enclosure: 同封物、封入されたもの
うわー!それだー!
スコープその1が同封されている
さっきのコードをもう一度。
var foo = function(){
var count = 0; // ここがスコープその1
return function(){
return count++; // ここがスコープその2
};
}; // foo()を実行すると、内側の関数が返ってくる
// 返って来た関数を実行すると、countがひとつずつ増える
var counter = foo();
counter(); // => 0
counter(); // => 1
2つのスコープがありますね。2つのスコープを気にしながら、9行目からの実行の流れを追ってみます。
- foo()を実行した
- [2行目] スコープその1においてcountは0になった
- [3行目] スコープその1において無名関数が定義された
- [3行目] スコープその1で作成された無名関数が返された(スコープその1サヨウナラ)
- [9行目] counterがその無名関数を参照するようになった
- [10行目] counter()を実行した
- [4行目] スコープその2においてcountがインクリメントされた
スコープその2のはずなのに、スコープその1のcountに手が届いています。スコープその1自体はもはや通り過ぎたのに、です。スコープその1が、スコープその2に封入されています!
重要なのでもう一度言います。
foo()から返ってきた関数には、スコープその1が同封されています!
これがEnclosure, 略してclosure!
結論
クロージャとは、まず第一にスコープが同封されていることを言うと考えればわかりやすいでしょう。その上で、Wikipediaに書いてある通り、そういう同封を使った関数や、同封されたスコープや、同封された変数のこともクロージャと呼ぶことがあると考えれば良いと思います。
ちなみに、スコープその2が同封された先は本当はスコープその2ではなく無名関数です。他にも理論的に正確ではないことを言ってる可能性があるので注意してください。