Javascript - 如何避免在做繁重工作时阻止浏览器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10180391/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 08:55:29  来源:igfitidea点击:

Javascript - how to avoid blocking the browser while doing heavy work?

javascript

提问by Gadi A

I have such a function in my JS script:

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

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

Maybe "doSomethingHeavy" is ok by itself, but repeating it 300 times causes the browser window to be stuck for a non-negligible time. In Chrome it's not that big of a problem because only one Tab is effected; but for Firefox its a complete disaster.

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

Is there any way to tell the browser/JS to "take it easy" and not block everything between calls to doSomethingHeavy?

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

回答by apsillers

You could nest your calls inside a setTimeoutcall:

您可以将您的电话嵌套在setTimeout电话中:

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

This queues up calls to doSomethingHeavyfor immediate execution, but other JavaScript operations can be wedged in between them.

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

A better solution is to actually have the browser spawn a new non-blocking process via Web Workers, but that's HTML5-specific.

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

EDIT:

编辑:

Using setTimeout(fn, 0)actually takes much longer than zero milliseconds -- Firefox, for example, enforces a minimum 4-millisecond wait time. A better approach might be to use setZeroTimeout, which prefers postMessagefor instantaneous, interrupt-able function invocation, but use setTimeoutas a fallback for older browsers.

使用setTimeout(fn, 0)实际花费的时间比零毫秒长得多——例如,Firefox强制执行最少 4 毫秒的等待时间。更好的方法可能是使用setZeroTimeout,它更喜欢postMessage即时的、可中断的函数调用,但setTimeout用作旧浏览器的后备。

回答by Rocket Hazmat

You can try wrapping each function call in a setTimeout, with a timeout of 0. This will push the calls to the bottom of the stack, and should let the browser rest between each one.

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

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

EDIT: I just realized this won't work. The ivalue will be the same for each loop iteration, you need to make a closure.

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

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

回答by ControlAltDel

You need to use Web Workers

你需要使用 Web Workers

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

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

There are a lot of links on web workers if you search around on google

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

回答by dyoo

We need to release control to the browser every so often to avoid monopolizing the browser's attention.

我们需要每隔一段时间向浏览器释放控制权,以避免垄断浏览器的注意力。

One way to release control is to use a setTimeout, which schedules a "callback" to be called at some period of time. For example:

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

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

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

Calling f1here will add the word helloto your document, schedule a pending computation, and then release control to the browser. Eventually, f2will be called.

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

Note that it's not enough to sprinkle setTimeoutindiscriminately throughout your program as if it were magic pixie dust: you really need to encapsulate the rest of the computation in the callback. Typically, the setTimeoutwill be the last thing in a function, with the rest of the computation stuffed into the callback.

请注意,setTimeout像魔法小精灵一样不加选择地在整个程序中撒播是不够的:您确实需要在回调中封装其余的计算。通常,setTimeout将是函数中的最后一件事,其余的计算都塞进回调中。

For your particular case, the code needs to be transformed carefully to something like this:

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

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

which will release control to the browser on every restOfComputation.

这将在每个restOfComputation.

As another concrete example of this, see: How can I queue a series of sound HTML5 <audio> sound clips to play in sequence?

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

Advanced JavaScript programmers need to know how to do this program transformation or else they hit the problems that you're encountering. You'll find that if you use this technique, you'll have to write your programs in a peculiar style, where each function that can release control takes in a callback function. The technical term for this style is "continuation passing style" or "asynchronous style".

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

回答by Bakudan

You can make many things:

你可以做很多事情:

  1. optimize the loops - if the heavy works has something to do with DOM access see this answer
    • if the function is working with some kind of raw data use typed arrays MSDNMDN
  2. the method with setTimeout() is called eteration. Very usefull.

  3. the function seems to be very straight forward typicall for non-functional programming languages. JavaScript gains advantage of callbacks SO question.

  4. one new feature is web workers MDNMSDNwikipedia.

  5. the last thing ( maybe ) is to combine all the methods - with the traditional way the function is using only one thread. If you can use the web workers, you can divide the work between several. This should minimize the time needed to finish the task.

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

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

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

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

回答by stefan bachert

I see two ways:

我看到两种方式:

a) You are allowed to use Html5 feature. Then you may consider to use a worker thread.

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

b) You split this task and queue a message which just do one call at once and iterating as long there is something to do.

b)您拆分此任务并将消息排队,该消息一次只执行一次调用,并在有事可做时进行迭代。

回答by vol7ron

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

回答by Stefan

There was a person that wrote a specific backgroundtask javascript library to do such heavy work.. you might check it out at this question here:

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

Execute Background Task In Javascript

在 Javascript 中执行后台任务

Haven't used that for myself, just used the also mentioned thread usage.

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