web-dev-qa-db-ja.com

JavaScript関数の宣言と評価の順序

これらの例の最初の例が機能しないのに、他の例はすべて機能するのはなぜですか?

// 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'); }
})();
78
jnylen

これはスコープの問題でも、クロージャーの問題でもありません。問題は、宣言の間の理解にあります。

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

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

フェーズ2:実行-解析されたコードが解釈されます。

関数宣言の構文は次のとおりです。

_function name (arguments) {code}
_

引数はもちろんオプションです(コードもオプションですが、それのポイントは何ですか?)。

しかし、JavaScriptではexpressionsを使用して関数を作成することもできます。関数式の構文は、式コンテキストで記述されていることを除いて、関数宣言に似ています。式は次のとおりです。

  1. _=_記号(またはオブジェクトリテラルの_:_)の右側にあるもの。
  2. 括弧内のすべて_()_。
  3. 関数へのパラメーター(これは実際には既に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の関数引数は常にalwaysであり、数値または文字列の場合は値によって、または他のすべての参照によって渡されるということです。したがって、setTimeoutは、実際に変数someFunctionに渡される(これはクロージャーが作成されることを意味する)のではなく、someFunctionが参照するオブジェクト(この場合は関数)のみを取得します。これは、クロージャを壊すために(ループなどで)JavaScriptで最も広く使用されているメカニズムです。

178
slebetman

Javascriptのスコープは関数ベースであり、厳密な字句スコープではありません。それはつまり

  • Somefunction1は囲んでいる関数の最初から定義されていますが、その内容は割り当てられるまで未定義です。

  • 2番目の例では、割り当ては宣言の一部であるため、先頭に「移動」します。

  • 3番目の例では、変数は匿名内部クロージャーが定義されているときに存在しますが、10秒後までに値が割り当てられるまで使用されません。

  • 4番目の例には、動作する2番目と3番目の理由があります

2
Javier

これは、トラブルを回避するための適切な手順に従う基本的なケースのように聞こえます。変数と関数を使用する前に宣言し、次のような関数を宣言します。

function name (arguments) {code}

Varで宣言しないでください。これは単にずさんで、問題につながります。使用する前にすべてを宣言する習慣になれば、問題のほとんどは大急ぎで消えます。変数を宣言するとき、すぐに有効な値で変数を初期化して、それらのいずれも未定義にならないようにします。また、関数が使用する前にグローバル変数の有効な値をチェックするコードを含める傾向があります。これは、エラーに対する追加の保護手段です。

これがどのように機能するかの技術的な詳細は、手g弾を使って遊ぶときの手ade弾の物理のようなものです。私の簡単なアドバイスは、そもそも手g弾で遊ばないことです。

コードの先頭にあるいくつかの単純な宣言は、これらの種類の問題のほとんどを解決する可能性がありますが、コードのクリーンアップが必要になる場合があります。

追加のメモ:
[。関数Aの前に宣言する必要があります.

したがって、最初にすべての関数を宣言し、次にグローバル変数を宣言してから、他のコードを最後に配置します。これらの経験則に従ってください。間違いはありません。これらのルールの実施を確実にするために、Webページの先頭に宣言を、本文に他のコードを配置することをお勧めします。

1
Terry Prothero

_someFunction1_は、setTimeout()の呼び出しが実行される時点ではまだ割り当てられていないためです。

someFunction3は同様のケースのように見えますが、この場合someFunction3()setTimeout()にラップする関数を渡すため、someFunction3()への呼び出しは後まで評価されません。 。

1
matt b