2017-02-07 5 views
0

私はforループでsetTimeoutを実行している問題に直面しています。 Iが出力javascriptのforループとタイムアウト関数

4期待

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
}

しかし、何らかの理由で、彼らはすべての出力5.

変数xは、forループのローカルスコープで定義されているので、私は、これはsetTimeoutメソッドのコールバックのためにカウントされないことがあり思いました。私はforループの外にxを定義してテストしました。

var x = 10 
 
for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
}

私は、この出力は10を与えることになる考え出し、まだそれはしませんでした。その後、xを定義することが理にかなっていると思いました。

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
} 
 
var x = 10

これが唯一の10。これは、コールバックは、すべてのforループの後に呼ばれている意味が実行されて返されていますか?そして、for-loopの実行後に変数が初期化されると、なぜfor-loopの親スコープにしか準拠しないのですか?何か不足していますか?

私はあなたがあなたの関数のための新しいスコープを作成する必要が

答えて

1

遅延値として1を指定しても、実際に関数が実行されるわけではないことに注意してください(つまり、timeoutFunctionコンテキストに関して) 1ミリ秒後にe。関数が実行できる最速の時間は約9ミリ秒です(ブラウザの内部構造に基づいています)。実際には、他のコードが実行されるまで関数を実行することはできません。

timeoutFunctionには、より高い範囲で宣言されたx変数への参照が含まれているため、予期しない出力が発生しています。これによりx変数のまわりにclosureが作成されます。そのため、関数が実行されるたびにxのコピーが得られませんが、より高いスコープで宣言されているのでx値を共有します。これはクロージャの典型的な副作用です。

問題を解決するための構文を調整するには、いくつかの方法があります...

xのコピーを作成し、各関数は、関数にxを渡すことで、自身のコピーを使用してみましょう。プリミティブ型(boolean、number、string)を渡すと、データのコピーが作成され、それが渡されます。これにより、共有されたxスコープへの依存が解消されます。あなたの最後の例は、この行います

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function(x) { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    // Because you are passing x into the function, a copy of x will be made for the 
 
    // function that is returned, that copy will be independent of x and a copy will 
 
    // be made upon each loop iteration. 
 
    setTimeout(timeoutFunction(x), 1) 
 
}

値を渡すために何の機能もないでしょうので、あなたのタイムアウト機能は、(他の関数を返していなかった場合に必要とされる同じことをもう一つの例に)。したがって、この例では、余分な機能を作成します。

for (var x = 0; x < 5; x++) { 
 
    
 
    // This time there is no nested function that will be returned, 
 
    function timeoutFunction(i) {  
 
      console.log(i);   
 
    } 
 
    
 
    // If we create an "Immediately Invoked Funtion Expression" (IIFE), 
 
    // we can have it pass a copy of x into the function it invokes, thus 
 
    // creating a copy that will be in a different scope than x. 
 
    (function(i){   
 
     setTimeout(function(){ 
 
     timeoutFunction(i); // i is now a copy of x 
 
     }, 1); 
 
    }(x)); 
 
}

あなたはECMAScriptの2015標準をサポートするブラウザを使用している場合、あなたは、単にlet xにあなたのループ内var x宣言を変更することができ、 xは、ループの各繰り返しでブロックレベルの有効範囲を取得します。

// Declaring a variable with let causes the variable to have "block-level" 
 
// scope. In this case the block is the loop's contents and for each iteration of the 
 
// loop. Upon each iteration, a new "x" will be created, so a different scope from 
 
// the old "x" is what's used. 
 
for (let x = 0; x < 5; x++) { 
 
    var timeoutFunction = function() { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(), 1) 
 
}

+0

すばらしい例とリンクをありがとう!最初は、閉鎖に関する発言を完全に理解することができませんでした。参照はより明確になり、あなたが意味するものを正確に理解しました:) "これは、割り当てられた関数がクロージャであり、関数の定義と関数のスコープのキャプチャされた環境で構成されているためです。ループによって作成されますが、それぞれが値が「x」の変数を持つ単一の環境を共有していますsetTimeoutコールバックが実行されると、その時点で「x」にアクセスするとこの動作が発生します " – jervtub

+0

@jervtub閉鎖は、通常、あなたの心を掴むために、より厳しいものの一つであり、彼らは理解するために少しの練習を取る。答えとして記入することを忘れないでください。がんばろう! –

1

...私は本当に不足しているのだろうか、この例では、

for (var x = 0; x < 5; x++) { 
 
    var timeoutFunction = function(x) { 
 
     return function() { 
 
      console.log(x) 
 
     } 
 
    } 
 
    setTimeout(timeoutFunction(x), 1) 
 
}

で作業を作る方法を知っているけれども与えられた反復から値を「覚えて」おく:

for (var x = 0; x < 5; x++) { 
    var timeoutFunction = (function(x) { 
     return function() { 
      console.log(x) 
     } 
    })(x) 
    setTimeout(timeoutFunction(), 1) 
} 

別の解決策は、ES2015 letを使用することです:

for (let x = 0; x < 5; x++) { 
    var timeoutFunction = function() { 
      console.log(x) 
     } 
    setTimeout(timeoutFunction(), 1) 
} 
+0

回答ありがとうBartekfr。私は本当にfor-loop内でスコープが失敗した理由を具体的に調べていたので、これを受け入れられた答えとして設定していません。私は 'let x'を参照するのが好きです。 – jervtub

1

使用使用しているので、ループが終了したためtimeoutFunctionが後に実行されますあなたのコードを持つように、このスニペット

for(let x = 0; x < 5; x++) { 
    var timeoutFunction = function() { 
     console.log(x); 
    }; 
    setTimeout(timeoutFunction, 1); 
}; 
console.log('For loop completed'); 

JavaScriptがマルチスレッドではありません

+0

解決策が正しくありません:timeoutFunctionが関数を返さないため、 'console.log'は即座に呼び出され、遅延はありません。テストするにはタイムアウトを15000に増やしてください。その番号は遅延なしで表示されます。 –

+0

テスト後にコードを修正しました – Raj

+0

あなたの答えはありがとうございました!シングルスレッド化されたことは、 '1000000'整数をループした後のロギングの遅延を私に説明します。 :) 私は 'let x'への参照が好きです。 – jervtub

関連する問題