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

IT技术 javascript
2021-01-20 02:13:53

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

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

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

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

6个回答

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

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

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

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

编辑:

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

为什么 setZeroTimeout 可能是更好的方法?有点不公平吧?这里 setTimeout 的目的与延迟无关,而是给其他代码运行的机会。
2021-03-16 02:13:53
@TobbeBrolinsetTimeout(fn, 0)这里的问题是(是?现在 6 岁了)setTimeout最短等待时间为 4 毫秒。如果您需要运行fn1000 次,那么浪费时间的上限为 4 秒。通过等待“零”毫秒,您仍然为其他进程在迭代之间对接腾出空间(您确实仍然使用 推迟任何排队等待的任务setZeroTimeout),但是如果没有其他任何东西在等待任务队列,您就不会浪费 4 毫秒.
2021-03-17 02:13:53
那应该是 setTimeout((function(i){
2021-03-24 02:13:53
在我的情况下,这两种技术都很有用(适用于不同种类的繁重工作)。谢谢!
2021-04-08 02:13:53
干得好。我想可能值得注意的是,浏览器在 期间会被冻结doSomethingHeavy,所以如果长时间操作,仍然会挂起,但会是 1/300 的时间。
2021-04-11 02:13:53

您可以尝试将每个函数调用包装在 a 中setTimeout,超时时间为 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);
   }
}
@Teemu:根据规范,这应该自动发生:If the currently running task is a task that was created by the setTimeout() method, and timeout is less than 4, then increase timeout to 4
2021-03-18 02:13:53
2021-04-01 02:13:53
0不再是可接受的延迟值,您至少需要使用4.
2021-04-05 02:13:53
@Teemu:[需要引用] 编辑:找到了developer.mozilla.org/en/DOM/...
2021-04-05 02:13:53
我刚刚为您找到了相同的 MDN 链接...它已经在这里了。最近,我一直在“翻译”一些旧的 HTA 以使用 IE9 实用程序,我不得不将每个小于 4 的超时和间隔延迟更改为 4。如果在下面,则不会调用任何调用。
2021-04-09 02:13:53

你需要使用 Web Workers

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

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

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

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

我看到两种方式:

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

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