如何在Javascript中做一个不会阻塞UI的循环

How to do a loop in Javascript that doesn't block the UI?

本文关键字:一个 循环 UI Javascript      更新时间:2023-09-26

我的页面上有一些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有一些异步,但我从来没有真正使用过它…

同时,元素的处理顺序必须始终与它们在数组中出现的顺序一致。

EDIT:我尝试了setTimeout()和"副本"下面的解决方案,但它仍然不能解决我的问题。我猜这是因为我的数组的大小不是很大,但每个元素的计算是相当大的。

我想要响应的UI是一个动画加载gif。由于每个项目的计算太大,所以动画很草率。

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

创建一个函数:

  1. 执行循环体执行的任何操作
  2. 增加计数器
  3. 测试计数器是否仍在长度范围内,如果是,则再次调用函数
此时,您将拥有与您已经拥有的功能相同的功能。

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

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

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代码在单独的线程中执行。你可以创建任意多的线程,所以如果你的任务很容易并行化,也可以考虑使用web worker。

如果你需要浏览器在你的JS代码中间更新页面,你需要屈服于浏览器,例如用一个setTimeout,在一个延续传递样式:

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

我倾向于这样做:

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);

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

注意现代浏览器都支持

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

还可以考虑使用JQuery/AJAX异步执行。