4

重複の可能性:
ループ内のJavascriptクロージャ-簡単な実用例

DOMへの要素の追加を制限するために、私のプロジェクトでsetTimeoutを試してみました(ページの読み込み中にUIがフリーズしないようにするため)。しかし、私は少し不可解なことに遭遇しました。このコードを考えると:

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout(function() {
        console.log("in timeout i is: " + i + " j is: " + j);
    }, i * 1000);
}

次の出力が得られます。

i is: 0 j is: 10
i is: 1 j is: 11
i is: 2 j is: 12
i is: 3 j is: 13
i is: 4 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14

iはforループの初期化でスコープされているため、タイムアウトのの値iが5であることは明らかです。しかし、jすべてのタイムアウト出力で14が発生するのはなぜですか?jループ内でスコープが設定されているため、タイムアウトで10、11、12、13、14が出力されると思います。どうすればその結果を達成できますか?

4

1 に答える 1

7

これは、JavaScriptではvar関数スコープがあるためです。

var宣言は、現在の実行コンテキストの先頭まで引き上げられます。つまり、関数内にある場合はvar、関数の実行コンテキスト内にスコープが設定されます。それ以外の場合は、プログラム(グローバル)実行コンテキストにスコープが設定されます。

ECMAScript 2015(別名ES6)letでは、ブロックスコープ変数を作成できるようになっていますが、広くサポートされていないため、参照用にリンクを残しておきます。

引き続き使用varしてループ内で「スコープ」を設定するための回避策は、クロージャーとも呼ばれる新しい実行コンテキストを作成することです。

function callbackFactory(i, j) {
    // Now `i` and `j` are scoped inside each `callbackFactory` execution context.
    return function() { // This returned function will be used by the `setTimeout`.
       // Lexical scope (scope chain) will seek up the closest `i` and `j` in parent
       // scopes, that being of the `callbackFactory`'s scope in which this returned
       // function has been initialized.
       console.log("in timeout i is: " + i + " j is: " + j);
    };
}
for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout( callbackFactory(i, j), i * 1000);
}

両方ijコールバックスコープ内をスコープsetTimeoutしたので、に渡されたときと同じ値を内に返しますcallbackFactory

ライブデモを参照してください。

同じことを行う別の方法は、ループ内にIIFEforを作成することです。これは通常読みやすいですが、JS(H | L)intはあなたに怒鳴ります。;)これは、ループ内に関数を作成するとパフォーマンスが低下すると見なされるためです。

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    (function(i, j) { // new execution context created for each iteration
        setTimeout(function() {
            console.log("in timeout i is: " + i + " j is: " + j);
        }, i * 1000);
    }(i, j)); // the variables inside the `for` are passed to the IIFE
}

for上記では、各反復で新しい実行コンテキストを作成しました。(デモ

最初のアプローチ(callbackFactory)と上記のIIFEを組み合わせて、3番目のオプションを作成することもできます。

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout(function(i, j) {
        return function() {
            console.log("in timeout i is: " + i + " j is: " + j);
        };
    }(i, j), i * 1000);
}

これは、関数の代わりにIIFEを使用しているだけcallbackFactoryです。これは読みやすいとは思えませんが、forループ内に関数を作成するため、パフォーマンスが低下しますが、これも可能であり、機能することに注意してください。

これらの3つのアプローチは、野生で非常に一般的に見られます。=]


ああ、主な質問に答えるのをほとんど忘れていました。callbackFactoryをループと同じスコープに配置し、forループの内側をスコープする代わりに、スコープチェーンに外側のスコープをiシークさせます。i

(function() {
    var i, j;
    function callbackFactory(j) {
    // the `j` inside this execution context enters it as a formal parameter,
    // shadowing the outer `j`. That is, it is independent from the outer `j`.
    // You could name the parameter as "k" and use "k" when logging, for example.
        return function() {
           // Scope chain will seek the closest `j` in parent scopes, that being
           // the one from the callbackFactory's scope in which this returned
           // function has been initialized.
           // It will also seek up the "closest" `i`,
           // which is scoped inside the outer wrapper IIFE.
           console.log("in timeout i is: " + i + " j is: " + j);
        };
    }
    for(i = 0; i < 5; i++) {
        j = i + 10;
        console.log("i is: " + i + " j is: " + j);
        setTimeout( callbackFactory(j), i * 1000);
    }
}());
/* Yields:
i is: 0 j is: 10  
i is: 1 j is: 11  
i is: 2 j is: 12  
i is: 3 j is: 13  
i is: 4 j is: 14  
in timeout i is: 5 j is: 10  
in timeout i is: 5 j is: 11  
in timeout i is: 5 j is: 12  
in timeout i is: 5 j is: 13  
in timeout i is: 5 j is: 14 */

フィドル

i読みやすさのためだけに、とj宣言をスコープの一番上に移動したことに注意してください。for (var i = [...]通訳が持ち上げるのと同じ効果があります。

于 2013-02-03T14:54:00.060 に答える