开发者

Problem with setTimeout()

This is my code. What I want it to do is write 0, wait one sec, write 1, wait one sec, write 2, wait one sec, etc. Instead it writes 5 5 5 5 5

for开发者_如何学运维(i = 0; i < 5; i++) {
    setTimeout("document.write(i + ' ')", 1000);
}

http://jsfiddle.net/Xb7Eb/


1) You set all the timeouts to last 1 second at the same time. The loop doesn't wait for the timeout to occur. So you have 5 timeouts that all execute at the same time.

2) When the timeouts execute, the loop is long since complete and i has become 5. So once they execute, they all print "5"

3) document.write() writes somthing onto the page, in the same place it executes. I.e. if you have <script>document.write("xyz")</script> in the middle of a piece of text, it'll write "xyz" in the middle of the text. The timeouts, however, are not necessarily anywhere on the page. They exist only in code.

Here's a solution that's as close to yours as possible: http://jsfiddle.net/rvbtU/1/

var container = document.getElementById("counter");
for(i = 0; i < 5; i++) {
    setTimeout("container.innerHTML += '" + i + " ';", 1000 * i);
}

However, that solution uses setTimeout's ability to evaluate a string as javascript, which is never a good idea.

Here's a solution that uses an anymous function instead: http://jsfiddle.net/YbPVX/1/

var container = document.getElementById("counter");
var writer = function(number) {
    return function() { container.innerHTML += String(number) + " "; };
}
for(i = 0; i < 5; i++) {
    setTimeout(writer(i), 1000 * i);
}

Edit: Forgot to save the 2nd fiddle. Whoops. Fixed now.


Most of the answers available are giving bad advice.* Specifically, you shouldn't be passing a string to setTimeout anymore (it still works, but it's discouraged), it's no longer 2000, there are better ways to do this.

setTimeout takes a function as the first parameter, and that's what you should do, however there are some issues when calling setTimeout in a loop.

This looks like it should work:

var i;
for ( i = 0; i < 5; i++ )
{
  setTimeout(function(){
    document.write( i + ' ' );
  }, 1000 * (i + 1) );
}

But it doesn't. The issue is that by the time setTimeout executes the function, the loop will have incremented i to 5, so you'll get the same value repeated.

There are a few fixes. If you're willing to risk a with statement, you could try the following:

var i;
for ( i = 0; i < 5; i++ )
{
  with( { i:i } )
  {
    setTimeout(function(){
      document.write( i + ' ' );
    }, 1000 * (i+1) );
  }
}

Note that with is typically discouraged just like passing string values to setTimeout, so I don't really suggest this method of doing things.

The better way is to use a closure:

var i;
for ( i = 0; i < 5; i++ )
{
  (function(i){
    setTimeout(function(){
      document.write( i + ' ' );
    }, 1000 * (i+1) );
  })(i);
}

To explain what's going on, the anonymous function wrapper (function(i){...code...}) executes immediately because it's wrapped in parens and passed i as a value:

(function(i){...code...})(i);

This forces the i variable that document.write uses to be a different one than what's being used in the for loop. You could even change the parameter used in the anonymous function wrapper if the difference gets too confusing:

(function(a){document.write(a+' ')})(i);

* when I started writing this question there were a number of answers describing how to fix the string to work with setTimeout, although they would technically work, they didn't include why they would work (because 'document.write("' + i + ' ");' evaluates i at the time of calling due to string concatenation, versus evaluating i at runtime like the previous version did), and they most certainly didn't mention that it's the bad old way of calling setTimeout.


try

var i = 1;
function timeout(){

    document.write(i + ' ');
    i++;
    if (i == 5) return;
    setTimeout(timeout, 1000);
}
 timeout();

http://jsfiddle.net/nnJcG/1/


You have a problem with clousures, you can try this:

var timeout = function(){
    var i = 0;
    return function(){                
        document.write(i+ ' ');
            i++;
            if(i!==5)
            setTimeout(timeout,1000);
    };
}();
setTimeout(timeout,1000);

Here is the example in jsBin http://jsbin.com/uloyuc/edit


First of all, NEVER pass a string to setTimeout. Use a function, it's much cleaner.

Second, you have to "close over" the loop value. I bet this is what you want.

for(var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      document.write(i + ' ')
    }, i * 1000);
  }(i));
}

See more about you a self executing function to close over a loop value here http://www.mennovanslooten.nl/blog/post/62


And just cause I love it, here is the equivalent in CoffeeScript whihc has the do keyword to help out with just this case.

for i in [0..4]
  do (i) ->
    setTimeout ->
      document.write "#{ i } "
    , i * 1000


You can also work with setInterval and clearInterval:

var i = 0;

var f = setInterval(function() {
    if(i == 4) clearInterval(f);
    document.write(++i + ' ');
}, 1000);

I think this code is very readable.


You could try like this:

var tick_limit = 5; // Or any number you wish representing the number of ticks
var counter = 0; // Or any number you wish 
var timer_interval = 1000; // Interval for the counter
var timer;

function timerTick()
{
 if(counter < tick_limit)
 {
   // Execute code and increase current count
   document.body.innerHTML+=(counter + ' '); // Append the counter value to the body of the HTML page
   counter++;
   timer = setTimeout(timerTick,timer_interval);
 }
 else
 {
  // Reset everything
  clearTimeout(timer);
  counter = 0;
 }
}

function startCounter()
{
 clearTimeout(timer); // Stop current timer
 timer = setTimeout(timerTick,timer_interval); // Start timer with any interval you wish
}
...
// Start timer when required
startCounter();
...

This way, calling the startCounter a number of times will result in a single timer executing the code


You're triggering five timeouts at the same time.

I like Pindatjuh's answer, but here's another fun way to do it.

This way starts the next timeout when the previous one is finished:

// Wrap everything in a self executing anonymous function so we don't pollute
//    the global namespace.
//
//    Note: Always use "var" statments or you will pollute the global namespace!
//    For example "for(i = 0; i < 5; i++)" will pollute the global namespace
//    unless you have "var i; for(i = 0; i < 5; i++)" or 
//    "for(var i = 0; i < 5; i++)" & all of that is not in the global namespace.
//    
(function() {

    // "i" will be available within doThis()
    //     you could also pass "i" as an argument
    var i = 0,
        doThis = function() {

            // setTimeout can take an anonymous function
            // or a regular function. This is better than
            // eval-ing a string.
            setTimeout(function() {
                document.write(i + ' ');
                ++i;

                // Do the function again if necessary
                if (i < 5) doThis();
            }, 1000);
        }

    // Let's begin! 
    doThis();

})();

Working Example

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜