86

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

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

4 に答える 4

192

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

JavaScript コードは、Netscape の最初のバージョンの JavaScript と 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が定義されていることを認識して作成します。デフォルトでは、作成されたすべての変数の値は未定義です。この時点では、コンパイラはまだ値を割り当てることができないことに注意してください。これは、値を返すためにインタープリターが何らかのコードを実行して割り当てる値を返す必要がある場合があるためです。この段階では、まだコードを実行していません。

フェーズ 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 が少し挿入されています。someFunctionsetTimeout に渡される時点で、宣言されているため、既に存在しています。


追加の説明:

setTimeout(someFunction, 10)が someFunction のローカル コピーと setTimeout に渡されたコピーの間にクロージャを作成しない理由を不思議に思うかもしれません。それに対する答えは、JavaScript の関数引数は常に、数値または文字列の場合は値渡し、それ以外の場合は参照渡しであるということです。そのため、 setTimeout は実際には渡された変数 someFunction を取得せず (クロージャが作成されることを意味します)、 someFunction が参照するオブジェクト (この場合は関数) のみを取得します。これは、JavaScript でクロージャ (ループなど) を解除するために最も広く使用されているメカニズムです。

于 2010-10-08T03:57:10.150 に答える
2

Javascript のスコープは関数ベースであり、厳密にはレキシカル スコープではありません。つまり、

  • Somefunction1 は囲んでいる関数の先頭から定義されていますが、その内容は代入されるまで未定義です。

  • 2 番目の例では、代入は宣言の一部であるため、一番上に「移動」します。

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

  • 4 番目の例には、機能する 2 番目と 3 番目の理由の両方があります。

于 2010-10-08T03:12:49.273 に答える
1

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

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

于 2010-10-08T03:07:56.550 に答える
1

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

function name (arguments) {code}

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

これらすべてがどのように機能するかの技術的な詳細は、手榴弾で遊ぶときの物理学のようなものです。私の簡単なアドバイスは、そもそも手榴弾で遊んではいけないということです。

コードの最初にいくつかの簡単な宣言を行うことで、この種の問題のほとんどを解決できる可能性がありますが、コードのクリーンアップが必要な場合もあります。

追記:
いくつかの実験を行ったところ、ここで説明した方法ですべての関数を宣言する場合、関数の順序は問題ではないようです。関数 A が関数 B を使用する場合、関数 B は関数 B を使用する必要はありません。関数 A の前に宣言します。

したがって、最初にすべての関数を宣言し、次にグローバル変数を宣言し、最後に他のコードを配置します。これらの経験則に従えば、間違いはありません。これらのルールを確実に適用するために、宣言を Web ページのヘッドに配置し、他のコードをボディに配置することも最善の方法です。

于 2012-07-24T23:48:42.173 に答える