これはスコープの問題でも、閉鎖の問題でもありません。問題は、宣言と式の間の理解にあります。
JavaScript コードは、Netscape の最初のバージョンの JavaScript と Microsoft の最初のコピーでさえ、2 つのフェーズで処理されます。
フェーズ 1: コンパイル - このフェーズでは、コードが構文ツリー (およびエンジンに応じてバイトコードまたはバイナリ) にコンパイルされます。
フェーズ 2: 実行 - 解析されたコードが解釈されます。
関数宣言の構文は次のとおりです。
function name (arguments) {code}
引数はもちろんオプションです (コードもオプションですが、その意味は何ですか?)。
ただし、JavaScript では、式を使用して関数を作成することもできます。関数式の構文は、式のコンテキストで記述されることを除いて、関数宣言に似ています。そして式は次のとおりです。
=
記号の右側(または:
オブジェクト リテラル) にあるもの。
- 括弧内は何でも
()
。
- 関数へのパラメーター (これは実際には 2 で既にカバーされています)。
宣言とは異なる式は、コンパイル段階ではなく実行段階で処理されます。このため、式の順序が重要になります。
したがって、明確にするために:
// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();
フェーズ 1: コンパイル。コンパイラは、変数someFunction
が定義されていることを認識して作成します。デフォルトでは、作成されたすべての変数の値は未定義です。この時点では、コンパイラはまだ値を割り当てることができないことに注意してください。これは、値を返すためにインタープリターが何らかのコードを実行して割り当てる値を返す必要がある場合があるためです。この段階では、まだコードを実行していません。
フェーズ 2: 実行。someFunction
インタープリターは、変数を setTimeoutに渡したいことを認識します。そしてそうです。残念ながら、 の現在の値someFunction
は未定義です。
// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
フェーズ 1: コンパイル。コンパイラは、 someFunction という名前の関数を宣言していることを認識し、それを作成します。
フェーズ 2: インタープリターはsomeFunction
、setTimeout に渡す必要があることを認識します。そしてそうです。の現在の値someFunction
は、コンパイルされた関数宣言です。
// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
フェーズ 1: コンパイル。コンパイラは、変数が宣言されていることを認識してsomeFunction
作成します。前と同じように、その値は未定義です。
フェーズ 2: 実行。インタプリタは匿名関数を setTimeout に渡し、後で実行します。この関数では、変数を使用していることがsomeFunction
わかるので、変数へのクロージャーを作成します。この時点では、 の値someFunction
はまだ定義されていません。次に、関数を に割り当てていることがわかりますsomeFunction
。この時点で、 の値はsomeFunction
未定義ではなくなりました。1/100 秒後に setTimeout がトリガーされ、someFunction が呼び出されます。その値はもはや未定義ではないため、機能します。
ケース 4 は、実際にはケース 2 の別のバージョンであり、ケース 3 が少し挿入されています。someFunction
setTimeout に渡される時点で、宣言されているため、既に存在しています。
追加の説明:
setTimeout(someFunction, 10)
が someFunction のローカル コピーと setTimeout に渡されたコピーの間にクロージャを作成しない理由を不思議に思うかもしれません。それに対する答えは、JavaScript の関数引数は常に、数値または文字列の場合は値渡し、それ以外の場合は参照渡しであるということです。そのため、 setTimeout は実際には渡された変数 someFunction を取得せず (クロージャが作成されることを意味します)、 someFunction が参照するオブジェクト (この場合は関数) のみを取得します。これは、JavaScript でクロージャ (ループなど) を解除するために最も広く使用されているメカニズムです。