【问题标题】:Confusing For-loop with timeout inside and closure将 For 循环与内部超时和闭包混淆
【发布时间】:2015-03-01 03:12:46
【问题描述】:

所以,我创建了一个容器 div,在该 div 内我有一个脚本动态创建 113 个 div,类名为“fragment”。现在,一切都井井有条,我特意将这 113 个 div 做得非常小,大约 5X5 像素。然后我把它们放在容器里,让它们拼出一个标题。所有的 div 都在一个名为 divs 的数组中。所以 divs 是一个包含 113 个元素的数组。

现在事情变得混乱了,我想让那些 div 隐藏在浏览器的不可见区域后面,我通过给“片段”绝对定位并设置它们隐藏的样式来实现这一点,例如:divs[45].style.left = -600';,之后,我创建了一个可以运行 for-loop 的脚本,并且在该 for-loop 中,我希望每个小元素在延迟后回到它的位置。但是,我不得不为此使用闭包,因为在 for 循环中插入 SetTimeout()inside 很奇怪。

for (i = 0; i < divs.length; i++) 
        {
            (function(j) 
             {
                setTimeout(function () 
                {
                    divs[j].style.left = divs[j].offsetLeft + 550;
                }, Math.floor(Math.random() * 1000));
            })(i); //Pass current value into self-executing anonymous function
        }   

我无法理解上面的代码。我以为写Math.floor(Math.random() * 1000)的区域是指定延迟以ms为单位。但是,如果我输入 1000,它将一次移动所有 113 个元素。我不明白这一点,在移动到数组中的下一个元素之前不应该等待 1 秒吗?顺便说一句,代码运行良好,最终发生的事情是元素随机移动,给标题带来了非常酷的效果。闭包对我来说已经足够令人困惑了,对我来说这似乎是一种非常奇怪的行为。

有人能解释一下为什么会发生这种情况吗,如果我取出Math.floor(Math.random() * 1000),为什么所有元素都会立即移动,而不是简单地让一个元素每秒移动一个左右... *

编辑 我的问题不是关于闭包,而是关于为什么取出Math.floor(Math.random() * 1000) 并用“1000”替换它会导致所有元素一次移动,而不是一个一个地移动,间隔为 1000 毫秒。

【问题讨论】:

  • 你需要在这里改变方法,有一个用 setTimeout 调用自身的函数......这样它会调用一次,然后等待......再次调用它并等待等等。您还可以传递一个或两个参数,例如 divs.length 和 var i;
  • Jon 已经回答了你的问题,所有的超时都一个接一个地触发,没有延迟,因为 for 循环不会等待每个超时,..

标签: javascript html arrays for-loop


【解决方案1】:

所有的 setTimeout 都在(几乎)同一时间被实例化,并且因为它们都有 1 秒的延迟,所以它们都在(几乎)同一时间启动。

【讨论】:

  • 哦,好吧,您介意更详细地解释一下吗?我认为 for 循环直到当前循环结束才结束。 while 循环会改变这一点吗?
  • for 循环继续进行。它不知道你在它的体内做异步工作。
  • for 循环确实结束了。 setTimeout() 调用异步执行。 while 循环也不例外。
  • JS 触发一个 setTimeout 并将其放入队列中,但它没有说 JS 将等待超时启动 - 这是挂起系统的一种可靠方法。
  • 你可以使用 setTimeout 来调用 setTimeout
【解决方案2】:

如果要按顺序添加元素,则需要递归执行。如果您使用for 循环,那么您将“同时”安排一堆计时器。他们都会同时开始倒计时,他们都会尽快结束,彼此不关心。

这是一个如何按顺序安排计时器的示例:

var things = [1, 2, 3];

(function next(array) {
    setTimeout(function () {
        var item = array[0],
            remaining = array.slice(1);
        console.log(item);
        if (remaining.length > 0) {
            next(remaining);
        }
    }, 1000);
}(things));

如果您正在执行大量异步工作,或者您希望处理类似于同步任务的异步任务,请尝试Async。它提供了一些不错的循环机制:

async.eachSeries(things, function (thing, callback) {
    setTimeout(function () {
        console.log(thing);
        callback();
    }, 1000);
}, function () {
    console.log('all done');
});

【讨论】:

  • 谢谢,乔恩告诉我同样的事情,但我有点慢,不明白为什么。我的理解是,直到其中的所有内容都执行完毕后,for 循环才会重新开始。所以我认为一秒钟的延迟会停止下一次迭代的执行。
  • 不。它只是继续前进。 for 循环对于同步编程比异步更有用。
  • 您不需要递归地执行此操作...我的回答是对 OP 的代码进行简单修改,并且效果很好。
  • 对于长时间运行的应用程序,您无法保证该时间间隔。在您使用setTimeout 的任何地方,由于 CPU 负载,您都会受到相同的计时器不准确性的影响。等到最后一个计时器完成后才开始下一个计时器只能保证您至少要等待指定的时间,这意味着运行时间较长的应用程序平均执行时间可能比应有的时间长得多,而平均延迟当同时启动所有超时时,执行之间的间隔会更加准确。
  • 对不起,我删除了我的最后一条评论,因为我的小提琴不够令人信服:这是一个更好的递归:jsfiddle.net/g9u4z0ex/1 和迭代:jsfiddle.net/a3kdLo7z/1
【解决方案3】:

好的,所以你想要这样的东西...http://jsfiddle.net/92xx7u21/

Javascript:

var oDivs = document.getElementsByTagName('div');

window.doit = function(iLength, iCounter){
    setTimeout(function () {
        $(oDivs[iCounter]).width(($(oDivs[iCounter]).width() - 10) + "px");
        iCounter++;
        if (iCounter<iLength) {
            doit(iLength, iCounter);
        } else {
         alert('done');   
        }
    }, 1000);
};

doit(oDivs.length, 0);

基本上这是一个自调用函数,你从底线开始它一次,然后它会调用自身,直到它达到元素的长度。它可以清理一下,但尽量保持简单.. 您需要再次添加随机时间,因为我将其保持在每次循环 1 秒。

由于我没有您的任何其他代码,因此我使用 jQuery 完成此操作以使其成为一个简单的示例,并且在这种情况下也只是调整宽度。

【讨论】:

    【解决方案4】:

    鉴于前面的答案,我很确定您理解;这只是对您的代码进行修改以执行您想要的操作,甚至可以帮助您进一步理解:

    for (i = 0; i < divs.length; i++) 
            {
                (function(j) 
                 {
                    setTimeout(function () 
                    {
                        divs[j].style.left = divs[j].offsetLeft + 550;
                    }, j * 1000); // simply multiply j by 1000
                })(i); //Pass current value into self-executing anonymous function
            }
    

    编辑(解释):setTimout 接受以毫秒为单位的延迟(1/1000 秒),j 介于 0divs.length-1 之间。通过将此值乘以动作之间延迟的毫秒数,您最终会得到从执行上述代码的那一刻开始等待每次执行的总毫秒数。

    【讨论】:

    • 有人能详细说明为什么这被否决了吗?
    • 我真的很喜欢你的解决方案,它非常聪明。顺便说一句,我没有对你投反对票,但我猜有人这样做是因为你没有解决其他两个答案的作用。如果可以的话,我会支持你,只是因为你通过改变一行解决了“潜在”问题。不错!
    • 好的,感谢您的反馈。只要有人欣赏答案,我就很高兴:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-08
    • 2016-06-16
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多