for循环中的setTimeout不会打印连续值

Ily*_*sis 254 javascript

我有这个脚本:

for (var i = 1; i <= 2; i++) {
    setTimeout(function() { alert(i) }, 100);
}
Run Code Online (Sandbox Code Playgroud)

但是3两次都被提醒,而不是1那时2.

有没有办法传递i,而不是将函数写为字符串?

Poi*_*nty 336

您必须安排为每个超时功能提供"i"的不同副本.

function doSetTimeout(i) {
  setTimeout(function() { alert(i); }, 100);
}

for (var i = 1; i <= 2; ++i)
  doSetTimeout(i);
Run Code Online (Sandbox Code Playgroud)

如果你不做这样的事情(并且这个想法有其他变化),那么每个定时器处理函数将共享相同的变量"i".当循环结束时,"i"的值是多少?这是3!通过使用中介函数,可以得到变量值的副本.由于超时处理程序是在该副本的上下文中创建的,因此它具有自己的私有"i".

编辑 - 随着时间的推移有一些评论,其中一些混淆是明显的,因为设置一些超时会导致处理程序同时发生.重要的是要了解设置计时器的过程- 呼叫setTimeout()- 几乎没有时间.也就是说,告诉系统"请在1000毫秒后调用此函数"将几乎立即返回,因为在计时器队列中安装超时请求的过程非常快.

因此,如果发出一连串的超时请求,如OP中的代码和我的答案中的情况,并且每个时间延迟值相同,则一旦该时间量已经过去,所有定时器处理程序将一个接一个地快速连续调用.

如果你需要的是每隔一段时间调用一次处理程序,你可以使用setInterval(),这被称为完全相同setTimeout()但在重复延迟请求数量后会多次触发,或者你可以建立超时并乘以时间迭代计数器的值.也就是说,要修改我的示例代码:

function doScaledTimeout(i) {
  setTimeout(function() {
    alert(i);
  }, i * 5000);
}
Run Code Online (Sandbox Code Playgroud)

(使用100毫秒超时,效果不会非常明显,所以我将数字提高到5000.)值i乘以基本延迟值,因此在循环中调用5次将导致延迟5秒,10秒,15秒,20秒和25秒.

更新

在2018年,有一个更简单的选择.由于新功能在范围中声明变量比函数更窄,原始代码如果这样修改就会起作用:

for (let i = 1; i <= 2; i++) {
    setTimeout(function() { alert(i) }, 100);
}
Run Code Online (Sandbox Code Playgroud)

let与此不同var,声明本身会导致i循环的每次迭代都有不同的声明.

  • 这是首选方法,因为它不会在循环体内导致函数定义.其他人会工作,但不是更好(即使他们确实显示了JS的惊人的坏屁股;)). (7认同)
  • @Pointy:这对我不起作用,js等待100然后整个for循环立即执行.如果我做错了,请纠正我. (3认同)

Dar*_*rov 158

您可以使用立即调用的函数表达式(IIFE)来创建闭包setTimeout:

for (var i = 1; i <= 3; i++) {
    (function(index) {
        setTimeout(function() { alert(index); }, i * 1000);
    })(i);
}
Run Code Online (Sandbox Code Playgroud)

  • 使用自我调用函数的好处的现实世界的好例子. (2认同)
  • 这不起作用:http://jsfiddle.net/Ljr9fq88/ (2认同)
  • IIFE只不过是无名函数和立即执行的便捷快捷方式 - 这就是接受的答案实际上所做的,完整的步骤,没有快捷方式 - 将函数调用包装在另一个函数中,因此内部函数获得外部函数争论的本地副本。 !! (2认同)
  • 你的请求是唯一对我有用的请求,我不希望所有请求都一起运行,一个接一个,无需等待。我想在一定时间后运行每个活动。谢谢! (2认同)

Meh*_*ash 32

这是因为!

  1. 超时循环完成后函数回调都运行良好.事实上,随着计时器的推移,即使它在每次迭代时都是setTimeout(..,0),所有这些函数回调仍然会在循环完成后严格运行,这就是为什么3被反映出来的原因!
  2. 尽管它们在每个循环迭代中被单独定义,但所有这两个函数都在相同的共享全局范围内关闭,该范围实际上只有一个i.

解决方案的通过使用执行的自功能(匿名一个或更好声明对于每次迭代单个范围IIFE)与具有的副本在里面,这样的:

for (var i = 1; i <= 2; i++) {

     (function(){

         var j = i;
         setTimeout(function() { console.log(j) }, 100);

     })();

}
Run Code Online (Sandbox Code Playgroud)

那个更清洁的人

for (var i = 1; i <= 2; i++) {

     (function(i){ 

         setTimeout(function() { console.log(i) }, 100);

     })(i);

}
Run Code Online (Sandbox Code Playgroud)

在每次迭代中使用IIFE(自执行函数)为每次迭代创建了一个新的范围,这使我们的超时函数回调有机会关闭每个迭代的新范围,其中一个具有正确的变量.它中的迭代值供我们访问.


har*_*rto 26

函数参数将setTimeout关闭循环变量.循环在第一个超时之前结束并显示当前值i,即3.

因为JavaScript变量只有函数作用域,所以解决方案是将循环变量传递给设置超时的函数.您可以声明并调用这样的函数:

for (var i = 1; i <= 2; i++) {
    (function (x) {
        setTimeout(function () { alert(x); }, 100);
    })(i);
}
Run Code Online (Sandbox Code Playgroud)

  • 要工作,它只需要将延迟乘以 i。像这样: setTimeout(function () { alert(x); }, i*100); (3认同)

Mev*_*abu 10

您可以使用setTimeout额外参数将参数传递给回调函数.

for (var i = 1; i <= 2; i++) {
    setTimeout(function(j) { alert(j) }, 100, i);
}
Run Code Online (Sandbox Code Playgroud)

注意:这不适用于IE9及以下浏览器.


Cod*_*ody 7

答案

我正在将它用于动画以将项目添加到购物车 - 当点击时,购物车图标从产品"添加"按钮浮动到购物车区域:

function addCartItem(opts) {
    for (var i=0; i<opts.qty; i++) {
        setTimeout(function() {
            console.log('ADDED ONE!');
        }, 1000*i);
    }
};
Run Code Online (Sandbox Code Playgroud)

注意持续时间是单位时间n epocs.

因此,从点击时刻开始,动画开始epoc(每个动画)是每个一秒单位乘以项目数的乘积.

epoc:https://en.wikipedia.org/wiki/Epoch_(reference_date )

希望这可以帮助!


Rag*_*dra 5

你可以使用bind方法

for (var i = 1, j = 1; i <= 3; i++, j++) {
    setTimeout(function() {
        alert(this);
    }.bind(i), j * 100);
}
Run Code Online (Sandbox Code Playgroud)

  • this 将数字设置为“this”。但观点已被采纳。与此类似,您也可以这样 `setTimeout(console.log.bind(console,i), 1000);` (3认同)