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

时间:2012-04-16 19:28:01

标签: javascript

我的JS脚本中有这样的功能:

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

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

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

8 个答案:

答案 0 :(得分:19)

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

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

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

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

修改

使用setTimeout(fn, 0)实际上需要比零毫秒更长的时间 - 例如,Firefox enforces a minimum 4-millisecond wait time。更好的方法可能是使用 setZeroTimeout ,它更喜欢postMessage进行即时,可中断的函数调用,但使用setTimeout作为旧浏览器的后备。

答案 1 :(得分:11)

您可以尝试在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);
   }
}

答案 2 :(得分:7)

您需要使用Web Workers

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

如果您在Google上搜索

,网络工作者上会有很多链接

答案 3 :(得分: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上向浏览器释放控制权。

作为另一个具体示例,请参阅:How can I queue a series of sound HTML5 <audio> sound clips to play in sequence?

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

答案 4 :(得分:2)

我看到两种方式:

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

b)你拆分这个任务并排队一条消息,这个消息只需要一次调用一次,并且只要有事情可以进行迭代。

答案 5 :(得分:2)

你可以做很多事情:

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

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

  4. 一项新功能是网络工作者MDN MSDN wikipedia

  5. 最后一件事(也许)是结合所有方法 - 用传统的方式,函数只使用一个线程。如果您可以使用Web工作者,则可以将工作分成几个。这应该最小化完成任务所需的时间。

答案 6 :(得分: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);
    }
}())

答案 7 :(得分:1)

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

Execute Background Task In Javascript

我没有使用它,只是使用了提到的线程用法。