why does javascript setTimeout() not work in a loo

2020-06-17 05:26发布

问题:

Consider the following code:

<!DOCTYPE html>
<html>
<head>
<script>
function timedText()
{
  var x=document.getElementById('txt');
  var t = new Array();
  t[1] = setTimeout( function(){x.value="2 seconds"}, 2000 );
  t[2] = setTimeout( function(){x.value="4 seconds"}, 4000 );
  t[3] = setTimeout( function(){x.value="6 seconds"}, 6000 );
}
function timedTextArr()
{
  var x=document.getElementById('txt');
  var t = new Array();
  for( var i = 0 ; i < 3 ; i++ ) {
    t[i] = setTimeout( function(){x.value=i*2+" seconds"}, i*2000 );
  }
}
</script>
</head>
<body>
<form>
<input type="text" id="txt" />
<input type="button" value="Display timed text!" onclick="timedText()" />
<input type="button" value="Display timed text Arr!" onclick="timedTextArr()" />
</form>
<p>Click on the button above. The input field will tell you when two, four, and six seconds have passed.</p>
</body>
</html>

Function timedText() works, but timedTextArr() doesn't. Both functions assign return values from setTimeout() to array elements. But in the for() loop, only the last timer works... and it works three times.

Is this a bug?

回答1:

This is not a bug, have a look at what closures are in Javascript.

Basically in your for loop the function

function(){x.value=i*2+" seconds"}

only "sees" one instance of the i variable.

So once the loop is over, i is equal to 3, so it is 3 for all functions.

You need to wrap the call in another anonymous function to create a scope, like this:

t[i] = setTimeout( (function(i){ return function(){x.value=i*2+" seconds"}})(i), i*2000 );

The outer function will create a new scope, and inside it i will be equal to the value of i in the loop and stay like this. You can try it out there: http://jsfiddle.net/6b68E/



回答2:

The i in your function refers to the i from the loop, which is 6 by the time any of the timeouts fire. You need to add a closure/scope:

  for( var i = 0 ; i < 3 ; i++ ) {
    (function(){    // create a closure (new scope)
      var _i = i;   // make a local copy of `i` from the outer scope
      t[i] = setTimeout( function(){x.value=_i*2+" seconds"}, i*2000 );
    })();
  }


回答3:

The reason you are getting the same results is setTimeout is asynchronous. Which means it doesn't run until the rest of the script is finished. Then once it runs the value of i is set to equal 3 so all the functions run and all they see is that i = 3.



回答4:

The main issue is that due to the asynchronous nature of timer functions, the loop is done running before the first timeout is called, and therefore i is set to 2 by the time the first timeout runs, and stays the same for the other two timeouts

To overcome this, you should consider refactoring the code to use an interval instead, which allows you to change the value of i in sync with the closures:

var i=1;

var handle = setInterval( function() {

x.value = (i*2) + "seconds";
i++;
if (i>3) clearInterval(handle);

}, 2000 );

Other than that the loop is running from 0 to 2, instead of 1 to 3, like in timedText()