【问题标题】:How to do a loop in Javascript that doesn't block the UI? [duplicate]如何在不阻塞 UI 的 Javascript 中执行循环? [复制]
【发布时间】:2014-06-12 20:41:11
【问题描述】:

我的页面上有一些 javascript 需要很长时间才能执行(10-30 秒之间)

代码基本上是这样的:

//Iterate over all the elements in the array
for(int i=0; i<array.length; i++){
   //Complex stuff for each element
}

问题是在执行此代码时,UI 没有响应。

有什么办法可以解决这个问题吗?我知道 javascript 具有某种异步性,但我之前从未真正使用过它...

此外,处理元素的顺序必须始终是它们在数组中出现的顺序。

编辑:我在“重复”中使用 setTimeout() 尝试了以下解决方案,但它仍然无法解决我的问题。我想这是因为我的数组的大小不是很大,但是每个元素的计算量都很大。

另外,我想要响应的 UI 是一个动画加载 gif。由于每个项目的计算量太大,所以动画马虎。

【问题讨论】:

  • 你可以将循环体转换为一个函数,调用它 next(),复制一个数组的反向副本,如 r=array.slice().reverse(),然后在 next() 中,得到通过调用 r.pop() 获取当前值。如果没有值,则返回,否则执行正常操作并在函数结束时调用 setTimeout(next, 10)。
  • 更多重复:stackoverflow.com/…

标签: javascript


【解决方案1】:

创建一个变量并将其设置为计数器的起始值。

创建一个函数:

  1. 不管循环体做什么
  2. 增加计数器
  3. 测试计数器是否仍在长度以下,如果是,则再次调用该函数

此时,您将拥有与已有功能相同的功能。

要在函数的每次调用之间暂停,并留出时间让其他函数触发,请将对函数的直接调用替换为对 setTimeout 的调用,并将函数用作第一个参数。

var counter = 0;
function iterator () {
    //Complex stuff for each element
    counter++;
    if (counter < array.length) {
        setTimeout(iterator, 5);
    } 
}

【讨论】:

  • 这个答案实际上很糟糕,因为 i 之前没有在代码中声明和使用。
【解决方案2】:

Javascript 使用一种叫做“事件循环”的东西。基本上,每个事件发生时都有一个线程执行代码。

有一个名为setTimeout 的函数,您可以使用它使您的Javascript 代码在循环的未来迭代中运行。一些开发人员用来在长时间运行的 Javascript 任务期间保持 UI 响应的一个技巧是定期调用代码以在超时为零的情况下运行。这会导致代码几乎立即执行而不会锁定事件循环。

例如,你可以写

var i = 0;
setTimeout(myFunction, 0);

function myFunction() {
    // complex stuff for each element.
    i++;
    if (i < array.length)
        setTimeout(myFunction, 0);
}

请注意,在现代浏览器上,只要 Javascript 代码正在执行,UI 就会无响应。有些浏览器甚至会弹出提示用户杀死脚本的消息;非常糟糕!

此外,在现代浏览器中,还有一个名为“Web Workers”的新功能。 Web Workers 允许在单独的线程中执行 Javascript 代码。您可以创建任意数量的线程,因此如果您的任务易于并行化,也可以考虑使用网络工作者。

【讨论】:

  • 您需要跳出循环以允许其他函数运行每次您绕过循环,而不仅仅是在开始时。
  • 你是对的。感谢您了解这一点。
【解决方案3】:

如果你需要浏览器在你的 JS 代码中间更新页面,你需要向浏览器让步,例如带有setTimeout,以延续传递风格:

function doStuff(i) {
    // do some stuff with index i
    if (i < array.length) {
        setTimeout(doStuff.bind(window, i + 1), 0);
    }
}

doStuff(0);

【讨论】:

  • 这不保证操作按问题要求按顺序发生。
  • @dandavis 如果array 从未发生变异,为什么不能保证迭代顺序?
  • 迭代顺序是有保证的,延迟的执行顺序是没有的,因为你使用了setTimeout。它通常会按编码工作,但不是 100% 的时间。
  • @dandavis 你能解释一下你的推理吗? setTimeout 将始终安排下一次迭代在当前迭代(以及任何 DOM 突变)完成后执行,因此代码将像按顺序编写一样执行,但在迭代之间会有暂停。当然,如果您在每次迭代中都有额外的 AJAX 调用,则需要添加同步,但问题中不需要这样做。
  • 仔细看,没关系。不知道为什么,但当我第一次看到它时,我感到很困惑。它实际上实现了与我的第一条评论相同的基本步骤......
【解决方案4】:

我倾向于这样做:

function doStuff(arr) {
  if (arr.length === 0) {
    // do whatever you need to do when you're done
    return;
  }

  // Do body of computation here

  // Or use setTimeout if your browser doesn't do setImmediate
  setImmediate(function () { doStuff(arr.slice(1)); });
}

doStuff(actualArrayOfThings);

基本上,分解循环体,并使用递归按顺序运行循环的每个部分。很容易将其分解为辅助函数,因为循环体本身是变化的。比如:

function eachAsync(arr, body) {
  if (arr.length === 0) {
    // do whatever you need to do when you're done
    return;
  }

  body(arr[0]);

  setImmediate(function () { eachAsync(arr.slice(1), body); });
}

eachAsync(actualArrayOfThings, doStuff);

【讨论】:

    【解决方案5】:

    如果您的任务是在不干扰用户界面的情况下处理数据,您可以使用 WebWorkers https://developer.mozilla.org/en/docs/Web/Guide/Performance/Using_web_workers

    请注意,现代浏览器支持它。

    【讨论】:

      【解决方案6】:

      如果 for 循环正在做一些整洁的工作,请使用 setTimeOut()。这取决于每次迭代执行的复杂内容。

      另外,考虑使用 JQuery/AJAX 来异步执行它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-09
        • 1970-01-01
        • 2018-07-29
        • 1970-01-01
        • 2017-11-08
        • 2019-12-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多