16

我对使用 setTimeout 和 each 迭代器感到困惑。如何重写以下内容,以便控制台在延迟 5 秒后输出每个名称?目前,下面的代码会在 5 秒后一次打印所有名称。我想:

1) 等待 5 秒,然后打印 kevin
2) 等待 5 秒,然后打印 mike
3) 等待 5 秒,然后打印 sally

var ary = ['kevin', 'mike', 'sally'];

_(ary).each(function(person){

  setTimeout(function(){
    console.log(person);
  }, 5000);    

});
4

7 回答 7

17

您有三个基本选项:

  1. For Loop +setTimeout
    ... 立即初始化每个人,但根据索引位置错开开始时间,这样它们就不会同时进行。
  2. setTimeout+ 条件递归
    ......每隔n秒检查一次 - 我会告诉你是否需要再做一次
  3. setInterval+ 有条件clearInterval
    的......每隔n秒继续运行 - 直到我告诉你停止

下面是每一个都用一个工作示例来充实:

1. 循环 +setTimeout

关于这一点的几点说明。这有点像开始一场接力赛,并提前向每个跑步者发出指令,让他们在 5:00 和 5:02 和 5:04 准确开始,不管他们身后的人是很久以前完成还是没有完成。 t 还没到。

此外,您不能使用常规for i=0 loop,因为 for 运算符没有定义新的函数范围。因此,在每个 for 循环中设置的对象值将适用于迭代。到调用 setTimeout 时,它只会使用最近的值。所以我们需要一个闭包来存储每个循环中的值。我用过Array.prototype.forEach(),但如果你想在 jQuery 或 Underscore 中使用 forEach 实现,那也可以。

function ArrayPlusDelay(array, delegate, delay) {
 
  // initialize all calls right away
  array.forEach(function (el, i) {
    setTimeout(function() {
        // each loop, call passed in function
        delegate( array[i]);

      // stagger the timeout for each loop by the index
      }, i * delay);
  })
 
}

// call like this
ArrayPlusDelay(['a','b','c'], function(obj) {console.log(obj)},1000)

2. setTimeout+ 条件递归

对于底部的两个选项,我们正在制作自己的循环,因此我们必须自己跟踪索引,初始化为零并自始至终递增。

对于这个,我们将 a) 调用setTimeout一次,b) 在索引位置计算数组,c) 检查数组中是否有更多元素,如果有,从 (a) 重新开始。

   
function ArrayPlusDelay(array, delegate, delay) {
  var i = 0
  
  function loop() {
  	  // each loop, call passed in function
      delegate(array[i]);
      
      // increment, and if we're still here, call again
      if (i++ < array.length - 1)
          setTimeout(loop, delay); //recursive
  }

  // seed first call
  setTimeout(loop, delay);
}

// call like this
ArrayPlusDelay(['d','e','f'], function(obj) {console.log(obj)},1000)

3. setInterval+ 有条件的clearInterval

注意:一旦调用,该函数setInterval将永远运行。它最初设置时的返回值将提供对间隔的引用,因此它通常与函数结合使用clearInterval以选择性地关闭它

function ArrayPlusDelay(array, delegate, delay) {
  var i = 0
  
   // seed first call and store interval (to clear later)
  var interval = setInterval(function() {
    	// each loop, call passed in function
      delegate(array[i]);
      
        // increment, and if we're past array, clear interval
      if (i++ >= array.length - 1)
          clearInterval(interval);
  }, delay)
  
}

ArrayPlusDelay(['x','y','z'], function(obj) {console.log(obj)},1000)


3* 秘密第四选项(最佳选项)

选项 1 和 2 是有风险的,因为一旦你启动了那辆火车,就没有办法在路上取消它(除了关闭浏览器)。如果您的委托中有一个大数组或繁重的负载,如果您需要它可能会很好地提供一些资源。通过保存来自 的引用setInterval,我们可以不断地访问迭代函数。我们只需要在调用我们的数组加延迟函数时返回上面的区间对象并保存即可。

function ArrayPlusDelay(array, delegate, delay) {
  var i = 0
  
   // seed first call and store interval (to clear later)
  var interval = setInterval(function() {
    	// each loop, call passed in function
      delegate(array[i]);
      
        // increment, and if we're past array, clear interval
      if (i++ >= array.length - 1)
          clearInterval(interval);
  }, delay)
  
  return interval
}

var inter = ArrayPlusDelay(['x','y','z'], function(obj) {console.log(obj)},1000)

然后,如果我们以后想清除它,只需将其扔到控制台中:

clearInterval(inter);

jsFiddle 中的所有 3 个演示

类似的堆栈溢出问题:

于 2017-08-03T12:38:04.897 回答
16

您可以创建一个名为的变量offset,使计时器为数组中的每个人多等待 5 秒,如下所示:

var ary = ['kevin', 'mike', 'sally'];

var offset = 0;
_(ary).each(function(person){

  setTimeout(function(){
    console.log(person);
  }, 5000 + offset);    
 offset += 5000;
});
于 2013-06-22T00:52:08.883 回答
7

你可以做

var ary = ['kevin', 'mike', 'sally'];

_(ary).each(function(person, index){

  setTimeout(function(){
    console.log(person);
  }, index * 5000);    
});

在不增加timeout值的情况下,您将使用完全相同的值初始化所有setTimeouts内容(这就是您看到所见的原因)。

于 2013-06-22T00:56:46.770 回答
1

each通常更适合立即发生的事情。

相反,如果您不介意更改数组,则可以将其用作队列:

var ary = ['kevin', 'mike', 'sally'];

setTimeout(function loop() {
    console.log(ary.shift());

    if (ary.length)
        setTimeout(loop, 5000);
}, 5000);

它会在未来继续调用loop5 秒,直到队列中没有任何东西。

于 2013-06-22T01:00:08.853 回答
0

array.forEach 接受一个回调函数,这意味着它已经创建了一个新的函数范围,并且由于 setTimeout 的性质是非阻塞的,它立即返回,您只需要在每次迭代时增加回调函数执行的延迟:

var ary = ['kevin', 'mike', 'sally'];

ary.forEach(function(person, index){
    setTimeout(function(){
        console.log(person);
    }, 5000 * (index + 1));   
})

如果你想用 for 循环达到同样的效果,你可以使用 IIFE 或 let 关键字

IIFE 示例:

var ary = ['kevin', 'mike', 'sally'];

for(var i = 1; i <= ary.length; i++){
    (function(i){
        setTimeout(function(){
            console.log(ary[i - 1]);
        }, 5000 * i); 
    })(i)
}

let 关键字示例:[ECMAScript 6]

var ary = ['kevin', 'mike', 'sally'];

for(let i = 1; i <= ary.length; i++){
    setTimeout(function(){
        console.log(ary[i - 1]);
      }, 5000 * i); 
}
于 2020-06-26T21:42:40.973 回答
0

最简单的方法:

const ary = ['kevin', 'mike', 'sally'];

ary.forEach((item, index) => {
    setTimeout(() => {
        console.log(item)
    }, index * 5000)
})

"kevin" 的索引为 1,因此它将在 5 秒后调用(1 * 5000 毫秒)

“mike”的索引为 2,因此将在 10 秒后调用(2 * 5000 ms)

"sally" 的索引为 3,因此将在 15 秒(3 * 5000 毫秒)后调用

于 2021-09-11T08:54:42.343 回答
-1

您可以将setInterval()与简单的加一计数器一起使用。

var ary = ['kevin', 'mike', 'sally'];

var i=0;
setInterval(function(){
    console.log(ary[i]);
    i++;
}, 5000);

但请注意,这将在 i 大于 2 后开始抛出错误。您需要在那里进行某种验证并确保清除间隔。

于 2013-06-22T00:56:13.330 回答