1

別のトピックを読んでいるときに、次のコードに出くわしました。

function loadme() {
 var arr = ["a", "b", "c"];
 var fs = [];
 for (var i in arr) {
   var x = arr[i];
   var f = function() { console.log(x) };
   f();
   fs.push(f);
 }
 for (var j in fs) {
   fs[j]();
 }
}

これにより、a、b、c、c、c、c が出力されます。著者が説明したように、ここでの問題は、 x var が関数の開始時に巻き上げられるため、ループで使用されたときにその値を保持しないことです。私が理解できないのは、c が 2 番目の console.log で x に 3 回割り当てられるのはなぜですか? 誰か説明できますか?

4

2 に答える 2

4

実際、この状況は非常に簡単に説明でき、JavaScript だけでなく多くのプログラミング言語で発生します。

a,b,c,c,c,c最初に出力シーケンスを取得し、最初の 3 つの要素を破棄しましょうa,b,c。これはf()、最初のforループ内で関数を実行したときに出力されるものだからです。

したがって、配列c,c,cを反復処理すると出力されるシーケンスが残ります。印刷されfsない理由は、最初のループ内にあるパラメーターがクロージャーによって参照によってキャプチャされるためです (ただし、定義が有効かどうかはわかりません) (JavaScript のすべての関数はいくつかのクロージャーであることを思い出してください)。選別)。a,b,cxforf()

これで、console.log(x)を呼び出すときに式が評価されるため、キャプチャされたパラメーターf()現在のx値が引数として渡されます。予想どおり、コードは機能しますがf()、最初のforループ内で呼び出すと、xが から割り当てられたばかりなarr[i]ので、 が得られますa,b,c。しかし、ループを終了すると、x(外側のスコープでは使用できなくても) によってキャプチャされf()ますが、最初のループの後に値c(最後の反復で取得した値) があり、それが評価されるときに評価されます。関数を 2 回繰り返します。

実際、これは多くの紛らわしいバグの原因となる可能性があるため、一部の言語ではこれを検出して開発者に通知できます (たとえば、C# コンパイラは、このような構造を検出すると、次の警告を生成します: Access to modified closure)。

この問題を解決するには、最初のループ内の現在の値をキャプチャする必要があります。xfor

for (var i in arr) {
    var x = arr[i];

    var f = (function(x) {
       return function() { console.log(x) };
    })(x);

    f();
    fs.push(f);
}

ここでは、IIFE (Immediately-Invoked Function Expression) を使用して の現在の値をキャプチャしますx

お役に立てれば。

于 2014-01-02T13:13:31.390 に答える
3
var x = arr[i];
var f = function() { console.log(x) };

この 2 行でconsole.log(x)は、 を印刷するだけでよいと考えていますx。したがって、3 つの関数はすべてその方法でのみ作成されます。したがって、関数をすぐに実行すると、 の値はx反復ごとに異なります。しかし、ループが終了すると、変数xは保持していた最後の値を保持し、動的に作成された関数が実行されると、その値が単に出力されxますc

xしたがって、これを修正するには、動的に作成された関数ごとに独自の のコピーが必要です。通常、ループ内で変化する変数の現在の状態を、次のように関数パラメーターで保持します。

var f = function(x) { return function() { console.log(x) } }(x);

ここで、関数を作成してすぐに実行します。これは別の関数を返し、返された関数は実際に の値を出力しますx

function(x) {
    return function() {
        console.log(x)
    }
}(x);

の値をパラメーターとしてラッパー関数に渡しているxため、現在の の値xが保持されます。

于 2014-01-02T13:03:14.980 に答える