如何将变量传递给setTimeout函数?

时间:2012-04-18 20:35:29

标签: javascript

我正在尝试设置五个交错的函数调用(相隔一秒发生)。那部分工作正常。什么行不通,我不能将值0到4传递给回调函数。它每次只传递'5'。我似乎无法弄清楚为什么以及如何解决它。

代码:

​function callback(num)
{
    console.log(num);
}

for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000)
    setTimeout(function() { callback(i); }, loadDelay);

结果:

5
5
5
5
5

期望的结果:

0
1
2
3
4

4 个答案:

答案 0 :(得分:12)

那是因为你创建了一个闭包。因此,您传递给setTimeout的函数共享相同的i个实例。在支持标准(不是IE)的浏览器中,您可以:

setTimeout(callback, loadDelay, i);

请参阅:     http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#timers

否则你必须实际bind函数的参数:

setTimeout(callback.bind(undefined, i), loadDelay);

请参阅:     https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

如果浏览器不支持ES5 bind方法,您可以在上面的链接中实现垫片,也可以手动执行以下操作:

setTimeout(function(index){
    return function() { callback(index) }
}(i), loadDelay);

但我会说使用bind更具可读性,并且值得实现垫片。你实际上可以使用它:https://github.com/kriskowal/es5-shim

在浏览器中添加es5功能(可能的地方),这些功能本身不支持es5。

答案 1 :(得分:4)

使用lambda / function表达式捕获当前值。例如

for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
  var doCall = function (j) {
    setTimeout(function() { callback(j); }, loadDelay);
  }
  doCall(i);
}

这里的问题是循环的所有迭代只有1 i值。 javascript中的变量具有函数范围,即使您可以在块内声明它们。这意味着i对整个函数都有效。

为了说明问题,请考虑以下代码与您的示例执行完全相同

var i;
for (i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
  ...
}

我的解决方案有效,因为它引入了一个新函数,因此引入了j的新变量生命周期。这会将i的当前值保存在函数中,以便在setTimeout回调

中使用

答案 2 :(得分:3)

由于变量作用域,您需要一个闭包才能传递i。查看this articlethis one as well,了解有关闭包的一些有用信息。

<强> Live Demo

function callback(num)
{
    console.log(num);
}

for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000)
    setTimeout((function(num){return function(){
           callback(num);
        }
    })(i), loadDelay);​

答案 3 :(得分:0)

setTimeout会产生一些奇怪的范围问题。 Frame.js旨在解决这种混淆,这也有效[更新]:

function callback(num) {
    console.log(num);
}

for (var i = 0, loadDelay = 1000; i < 5; ++ i, loadDelay += 1000) {
    Frame(function(next, i){
        setTimeout(function() { callback(i); }, loadDelay);
        next();
    }, i);
}
Frame.init();