JS超时未触发

时间:2012-05-15 03:44:10

标签: javascript timer

似乎无法弄明白,如何防止外循环继续,直到使用setTimeout执行内循环:

    function $(s) { return document.getElementById(s); }
    var i = 0;
    for (i=0; i<6; i++){
        $('t'+i).className = 'show';
        cl('t'+i);
        for (var j=0; j<50; j++){
            cl('b'+i+'='+j+'%');
            setTimeout(function(){ $('b'+i).style.width = j+'%';},200);
        }
    }

这一小段代码应首先使元素t0可见,然后以1%步长设置另一个元素b0的宽度,时间间隔为200ms,然后继续t1,b1,t2,b2等等

但是没有200ms延迟,整个代码立即执行。

---编辑---

我没有很好地解释,这就是我想要做的事情:

1. show element Ax
2. increase element Bx width by 1% every 200ms until 50%
3. wait until element Bx reaches 50% before continuing
4. increment x
5. goto 1

3 个答案:

答案 0 :(得分:4)

两个问题:

  • 超时功能会看到错误的ij
  • 它们都在同一时间运行(200毫秒后)

超时功能会看到错误的ij

主要问题是您传入setTimeout的功能对ij变量有持久的引用,副本从创建函数时开始。这意味着所有函数在运行时都会看到ij的值,分别为650。这称为“关闭”这些变量(该函数称为“闭包”)。

解决这个问题的常用方法是以一种不会改变的方式创建函数。有几种方法可以做到这一点;我最喜欢的是使用工厂功能:

function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
    $('t'+i).className = 'show';
    cl('t'+i);
    for (var j=0; j<50; j++){
        cl('b'+i+'='+j+'%');
        setTimeout(makeHandler(i, j), 200);
    }
}
function makeHandler(ivalue, jvalue) {
    return function(){ $('b'+ivalue).style.width = jvalue+'%';};
}

现在我们调用 makeHandler,它会向我们返回一个关闭ivaluejvalue的函数,该函数不会更改。或者上面的改进,让我们在完成它时处理制造商功能:

function $(s) { return document.getElementById(s); }
var i = 0;
var makeHandler = function(ivalue, jvalue) {
    return function(){ $('b'+ivalue).style.width = jvalue+'%';};
};
for (i=0; i<6; i++){
    $('t'+i).className = 'show';
    cl('t'+i);
    for (var j=0; j<50; j++){
        cl('b'+i+'='+j+'%');
        setTimeout(makeHandler(i, j), 200);
    }
}
makeHandler = undefined;

如果你可以依赖ES5功能(由于你所针对的环境,或者因为你已经包含了ES5垫片),你可以使用新的Function#bind获得相同的效果。 Function#bind创建了一个新函数,就像makeHandler一样,但引擎总是有可能优化一点:

function $(s) { return document.getElementById(s); }
var i = 0;
for (i=0; i<6; i++){
    $('t'+i).className = 'show';
    cl('t'+i);
    for (var j=0; j<50; j++){
        cl('b'+i+'='+j+'%');
        setTimeout(handler.bind(undefined, i, j), 200);
    }
}
function handler(){
    $('b'+ivalue).style.width = jvalue+'%';
}

bind的第一个参数是this在函数中应该是什么;在我们的例子中我们不关心,所以我已经指定了undefined(这意味着该函数将在浏览器上引用this引用全局对象 - window - 除非这是严格模式代码,在这种情况下,this实际上是undefined)。

它们都在同一时间运行(200毫秒后)

您的所有功能都计划在上述代码后运行200ms。所以他们会。 :-)如果你想将它们分开,你需要为每次setTimeout的呼叫增加200ms。我们可以乘以ij

setTimeout(makeHandler(i, j), (200 * i * j) + 200);

现在第一个将在200ms之后运行,第二个将在200ms之后运行,等等。整个过程将需要大约一分钟才能完成。这假设您希望第一个元素增长,然后是下一个元素,然后是下一个元素,而不是所有六个元素彼此平行增长。

或者,您可能希望让每个函数调用其后继函数。这可能就是我要做的。因此,不是安排300个函数调用,只需安排一个函数调用,当它发生时,安排下一个:

function $(s) { return document.getElementById(s); }

// Our counters are here
var i = 0, j = 0;

// This handles the outer portion of the loop (mostly)
function outer() {
    $('t'+i).className = 'show';
    cl('t'+i);
    j = 0;
    // Schedule the first inner portion 200ms from now
    setTimeout(inner, 200);
}

// This handles the inner portion of the loop (mostly)
function inner() {
    // Do this bit
    $('b'+i).style.width = j+'%';
    ++j;
    if (j < 50) {
       // Continue the inner loop in 200ms
       setTimeout(inner, 200);
    }
    else {
       // Set up next outer loop
       j = 0;
       ++i;
       if (i < 6) {
           // Not done yet, keep going
           setTimeout(outer, 200);
       }
     }
}

// Kick it off
setTimeout(outer, 200);

在上文中,我也提到了这些方面:

$('t'+i).className = 'show';
cl('t'+i);

...进入延迟代码,我怀疑是合适的。

更多探索:

答案 1 :(得分:3)

这是因为你关闭了ij

setTimeout(function(i, j) { 
    return function() {
        $('b'+i).style.width = j+'%';
    }
}(i, j), 200);

答案 2 :(得分:0)

setTimeout(function () { alert('Refreshing…'); }, 2000);

这意味着在两秒钟(2000毫秒)后显示警告