25

我的 JS 脚本中有这样一个函数:

function heavyWork(){
   for (i=0; i<300; i++){
        doSomethingHeavy(i);
   }
}

也许“doSomethingHeavy”本身没问题,但重复 300 次会导致浏览器窗口卡住一段不可忽略的时间。在 Chrome 中,这不是什么大问题,因为只有一个 Tab 会受到影响;但对于 Firefox 来说,这完全是一场灾难。

有没有办法告诉浏览器/JS“放轻松”而不是阻止调用doSomethingHeavy之间的所有内容?

4

9 回答 9

24

您可以将调用嵌套在setTimeout调用中:

for(...) {
    setTimeout(function(i) {
        return function() { doSomethingHeavy(i); }
    }(i), 0);
}

这会将调用排队doSomethingHeavy等待立即执行,但其他 JavaScript 操作可以插入它们之间。

更好的解决方案是让浏览器通过Web Workers生成一个新的非阻塞进程,但这是特定于 HTML5 的。

编辑:

使用setTimeout(fn, 0)实际上需要比零毫秒更长的时间——例如,Firefox强制要求至少 4 毫秒的等待时间。更好的方法可能是使用setZeroTimeout,它更喜欢postMessage瞬时、可中断的函数调用,但setTimeout用作旧浏览器的后备。

于 2012-04-16T19:34:01.550 回答
13

您可以尝试将每个函数调用包装在 asetTimeout中,超时为 0。这会将调用推到堆栈的底部,并且应该让浏览器在每个调用之间休息。

function heavyWork(){
   for (i=0; i<300; i++){
        setTimeout(function(){
            doSomethingHeavy(i);
        }, 0);
   }
}

编辑:我刚刚意识到这行不通。每次循环迭代的i值都相同,您需要进行闭包。

function heavyWork(){
   for (i=0; i<300; i++){
        setTimeout((function(x){
            return function(){
                doSomethingHeavy(x);
            };
        })(i), 0);
   }
}
于 2012-04-16T19:32:09.287 回答
10

你需要使用 Web Workers

https://developer.mozilla.org/En/Using_web_workers

如果你在谷歌上搜索,网络工作者有很多链接

于 2012-04-16T19:33:03.267 回答
3

我们需要时常将控制权释放给浏览器,以避免垄断浏览器的注意力。

释放控制的一种方法是使用setTimeout,它安排在某个时间段调用“回调”。例如:

var f1 = function() {
    document.body.appendChild(document.createTextNode("Hello"));
    setTimeout(f2, 1000);
};

var f2 = function() {
    document.body.appendChild(document.createTextNode("World"));
};

在此处调用f1会将单词添加hello到您的文档中,安排待处理的计算,然后将控制权释放给浏览器。最终,f2将被调用。

请注意,在整个程序中不加选择地洒在整个程序中是不够的,setTimeout就好像它是神奇的小精灵尘埃:您确实需要将其余的计算封装在回调中。通常,setTimeout将是函数中的最后一件事,其余的计算填充到回调中。

对于您的特定情况,需要将代码仔细转换为以下内容:

var heavyWork = function(i, onSuccess) {
   if (i < 300) {
       var restOfComputation = function() {
           return heavyWork(i+1, onSuccess);
       }
       return doSomethingHeavy(i, restOfComputation);          
   } else {
       onSuccess();
   }
};

var restOfComputation = function(i, callback) {
   // ... do some work, followed by:
   setTimeout(callback, 0);
};

这将在每个restOfComputation.

作为另一个具体示例,请参阅:如何将一系列声音 HTML5 <audio> 声音剪辑排队以按顺序播放?

高级 JavaScript 程序员需要知道如何进行这种程序转换,否则他们会遇到您遇到的问题。您会发现,如果您使用这种技术,您将不得不以一种特殊的风格编写程序,其中每个可以释放控制的函数都接收一个回调函数。这种风格的技术术语是“延续传递风格”或“异步风格”。

于 2012-04-16T19:43:08.523 回答
2

我看到两种方法:

a) 您可以使用 Html5 功能。那么你可以考虑使用工作线程。

b)您拆分此任务并将一条消息排队,该消息一次只执行一个调用,并且只要有事情要做就迭代。

于 2012-04-16T19:31:11.893 回答
2

你可以做很多事情:

  1. 优化循环 - 如果繁重的工作与 DOM 访问有关,请参阅此答案
    • 如果函数正在处理某种原始数据,请使用类型化数组MSDN MDN
  2. 带有 setTimeout() 的方法称为eteration。非常有用。

  3. 对于非函数式编程语言,该函数似乎非常简单。JavaScript 获得了回调SO question的优势。

  4. 一项新功能是网络工作者MDN MSDN 维基百科

  5. 最后一件事(也许)是结合所有方法 - 与传统方式的函数只使用一个线程。如果您可以使用网络工作者,您可以将工作分配给几个人。这应该最大限度地减少完成任务所需的时间。

于 2012-04-17T12:06:47.880 回答
1
function doSomethingHeavy(param){
   if (param && param%100==0) 
     alert(param);
}

(function heavyWork(){
    for (var i=0; i<=300; i++){
       window.setTimeout(
           (function(i){ return function(){doSomethingHeavy(i)}; })(i)
       ,0);
    }
}())
于 2012-04-16T19:29:27.767 回答
1

有人编写了一个特定的 backgroundtask javascript 库来完成如此繁重的工作。你可以在这里查看这个问题:

在Javascript中执行后台任务

我自己没有使用过,只是使用了也提到的线程用法。

于 2012-04-16T19:30:26.547 回答
0

2022答案

有一个名为的新功能requestIdleCallback,您可以在其中运行仅在没有其他函数占用事件循环时才会执行的函数,这意味着对于不太重要的繁重工作,您可以安全地执行它而不会影响主线程(假设任务占用小于16ms,也就是一帧。否则必须批量处理)

我编写了一个函数来执行一系列操作而不影响主线程。您还可以随时传递 shouldCancel 回调来取消工作流。它将回退到 setTimeout:

export const idleWork = async (
  actions: (() => void)[],
  shouldCancel: () => boolean
): Promise<boolean> => {
  const actionsCopied = [...actions];
  const isRequestIdleCallbackAvailable = "requestIdleCallback" in window;

  const promise = new Promise<boolean>((resolve) => {
    if (isRequestIdleCallbackAvailable) {
      const doWork: IdleRequestCallback = (deadline) => {
        while (deadline.timeRemaining() > 0 && actionsCopied.length > 0) {
          actionsCopied.shift()?.();
        }

        if (shouldCancel()) {
          resolve(false);
        }

        if (actionsCopied.length > 0) {
          window.requestIdleCallback(doWork, { timeout: 150 });
        } else {
          resolve(true);
        }
      };
      window.requestIdleCallback(doWork, { timeout: 200 });
    } else {
      const doWork = () => {
        actionsCopied.shift()?.();
        if (shouldCancel()) {
          resolve(false);
        }

        if (actionsCopied.length !== 0) {
          setTimeout(doWork);
        } else {
          resolve(true);
        }
      };
      setTimeout(doWork);
    }
  });

  const isSearchSuccessful = await promise;
  return isSearchSuccessful;
};

上面将执行一个函数列表。该列表可能非常长且昂贵,但只要每个单独的任务都在 16 毫秒以下,它就不会影响主线程。警告,因为并非所有浏览器都支持这一点,但 webkit 支持

于 2022-02-28T20:08:52.377 回答