ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

JavaScript と関数とクロージャ

JavaScript でどれがクロージャなのか、という話が興味深かったです!

ほむほむ。 クロージャなのかどうか考える以前に、どういう挙動を示すのかを考えるのが難しいですよね>< eval の挙動の違いなんかは狂気を感じざるを得ません!

そうそう、"use strict" の有無 (strict コードかどうか) でも eval コードの変数環境の初期化処理が変化します。 難しいですね!!

var e1 = (function () {
    var msg = "関数の中です";
    return eval("var msg; (function() { return msg; })");
}).call(this);
console.log(e1()); //=> "関数の中です"

// strict コードだと eval コード用に新たなスコープが作られるので、
// eval コード中の msg (undefined) が関数内の msg ("関数の中です") を隠ぺいする
var e2 = (function () {
    "use strict";
    var msg = "関数の中です";
    return eval("var msg; (function() { return msg; })");
}).call(this);
console.log(e2()); //=> undefined

この挙動についても ECMA-262 5.1th の 10.4.2 節 に書かれています。

  • 3. If the eval code is strict code, then
    • a. Let strictVarEnv be the result of calling NewDeclarativeEnvironment passing the LexicalEnvironment as the argument.
    • b. Set the LexicalEnvironment to strictVarEnv.
    • c. Set the VariableEnvironment to strictVarEnv.
ECMA-262 5.1th の 10.4.2 節

それはともかくクロージャについて

関数の中に関数を書ける、というのは説明するのに都合が良いというだけで、クロージャの本質ではないと思うわけですが、 この考えだとどうしても世間とずれが生じているように思えます。 はっきりとした答えが出せないのです。皆さんはどうお考えでしょうか? 何らかの反応があると嬉しいです。

回答:どれが「クロージャ」でしょうか? - hogehoge @teramako

クロージャの説明として 『ローカル変数を参照している関数内関数』 みたいなことが書かれているのは確かによく目にしますが、説明としては微妙だなー、という気はします。

  • ECMAScript (JavaScript) ではスコープを形作るのは主に関数
  • スコープチェインのすぐ上にグローバルスコープがある場合はクロージャだとみなしてもクロージャじゃないとみなしても実用上どちらでもよい (ECMA-262 の仕様的には原理上はクロージャだと思うけど)

という 2 点から、JavaScript における クロージャの説明としては 『関数内で定義された関数がクロージャ (であり、外側の関数のローカル変数にアクセスできるとかなんとか)』、という説明がわかりやすく一般受けしやすいものなんだと思います。 しかし、『関数内で定義された』 ということは特に本質的ではないですよね。

原理上は全ての ECMAScript の関数はクロージャ?

本質的には、クロージャとは

  • 関数オブジェクトであり、
  • 自身が生成された環境の変数束縛を保持しているもの

だと思いますので、原理上は ECMAScript の関数は全てクロージャと言って良いのではないかなーと思ったりしてます。 (よくわかんないです。)

  • とはいえグローバルスコープで定義された関数をクロージャとみなすかどうかは微妙な感じはする
    • 実用上どちらでもよいし、あえてクロージャだとみなしても特に利点はない気がする
追記 : Function コンストラクタで作られた関数はクロージャじゃないですね

書いてから気づいたけど Function コンストラクタで作られた関数はクロージャじゃないし、「全ての関数はクロージャ」 というのは間違ってました。 ううう。

少なくとも 『関数内で定義された関数がクロージャだ』、というのはおかしいと思う

例えば catch ブロック中で定義された関数は catch ブロックのスコープを持つわけなので、たとえ関数の中で定義されてなくてもそれはクロージャなわけなので、『関数内で定義された関数』 であることはクロージャであるための必要条件ではないですよね。

var count = 100;
try {
    throw 0;
} catch (count) {
    var countup = function () { return ++count };
}

// 関数 countup はグローバルスコープではない変数環境への参照を保持している
// countup はクロージャ!
console.log(countup()); //=> 1
console.log(countup()); //=> 2
console.log(countup()); //=> 3

とかなんとか考えたりしました。