1. ホーム
  2. javascript

[解決済み] JavaScriptの関数宣言と評価順序

2023-05-18 17:34:06

質問

これらの例のうち、最初のものは動作しませんが、他のものはすべて動作するのはなぜですか?

// 1 - does not work
(function() {
setTimeout(someFunction1, 10);
var someFunction1 = function() { alert('here1'); };
})();

// 2
(function() {
setTimeout(someFunction2, 10);
function someFunction2() { alert('here2'); }
})();

// 3
(function() {
setTimeout(function() { someFunction3(); }, 10);
var someFunction3 = function() { alert('here3'); };
})();

// 4
(function() {
setTimeout(function() { someFunction4(); }, 10);
function someFunction4() { alert('here4'); }
})();

どのように解決するのですか?

これはスコープの問題でもクロージャーの問題でもありません。問題は 宣言 .

JavaScriptのコードは、Netscapeの最初のバージョンやMicrosoftの最初のコピーでさえ、2つのフェーズで処理されます。

フェーズ 1: コンパイル - このフェーズでは、コードは構文木 (およびエンジンに依存するバイトコードまたはバイナリ) にコンパイルされます。

フェーズ 2: 実行 - パースされたコードは次に解釈されます。

関数の構文 宣言

function name (arguments) {code}

引数はもちろん任意です(コードも任意ですが、何の意味があるのでしょうか?)

しかし、JavaScriptでは . 関数式の構文は、式コンテキストで書かれることを除けば、関数宣言に似ています。そして、式は

  1. の右にあるものはすべて = 記号(または : を使用することができます。)
  2. 括弧の中にあるもの () .
  3. 関数へのパラメータ (これは実際にはすでに 2 でカバーされています)。

とは異なり 宣言 はコンパイルフェーズではなく実行フェーズで処理されます。そしてこのため、式の順序は重要です。

そこで、明確にするために


// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();

フェーズ1:コンパイル。コンパイラは、変数 someFunction が定義されていることを確認し、それを作成します。デフォルトでは、作成されたすべての変数の値はundefinedである。コンパイラはこの時点ではまだ値を代入できないことに注意してください。なぜなら、代入する値を返すために、インタプリタが何らかのコードを実行する必要があるかもしれないからです。そして、この段階ではまだコードは実行されていません。

第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 でクロージャを解除するために最も広く使われているメカニズムです (たとえばループの中で)。