6

我有一个长期运行的功能。它遍历一个大数组并在每个循环中执行一个函数。

longFunction : function(){
       var self = this;
       var data = self.data;

       for(var i=0; len = data.length; i<len; i++){
              self.smallFunction(i);
       }
},
smallFunction : function(index){

// Do Stuff!

}

在大多数情况下,这很好,但是当我处理大约 1500 左右的数组时,我们会收到一条 javascript 执行警报消息。

所以我需要打破这个。我的第一次尝试是这样的:

longFunction : function(index){
       var self = this;
       var data = self.data;


      self.smallFunction(index);

      if(data.slides[index+1){
         setTimeout(function(){
            self.longFunction(index+1);
         },0);
      }
      else {
               //WORK FINISHED
      }

},
smallFunction : function(index){

// Do Stuff!

}

所以在这里我要删除循环并引入一个自调用函数,每次迭代都会增加它的索引。为了将控制权返回给主 UI 线程以防止 javascript 执行警告方法,我添加了一个setTimeout允许它在每次迭代后有时间更新的方法。问题是,使用这种方法完成实际工作需要花费 10 倍的时间。看起来正在发生的事情是,虽然setTimeout设置为 0,但它实际上等待的时间更像是 10 毫秒。它在大型阵列上建立得非常快。删除setTimeout和 letlongFunction调用本身可以提供与原始循环方法相当的性能。

我需要另一种解决方案,它具有与循环相当的性能,但不会导致 javascript 执行警告。不幸的是,在这种情况下不能使用 webWorkers。

需要注意的是,在此过程中我不需要完全响应的 UI。足以每隔几秒更新一个进度条。

将它分解成大块循环是一种选择吗?即一次执行 500 次迭代,停止,超时,更新进度条,执行下一个 500 等等.. 等等..

有更好的吗?

回答:

唯一的解决方案似乎是将工作分块。

通过将以下内容添加到我的自调用函数中,我允许 UI 每 250 次迭代更新一次:

 longFunction : function(index){
           var self = this;
           var data = self.data;


          self.smallFunction(index);

          var nextindex = i+1;

          if(data.slides[nextindex){
            if(nextindex % 250 === 0){
             setTimeout(function(){               
                self.longFunction(nextindex);
             },0);
            }
            else {
                self.longFunction(nextindex);
            }
          }
          else {
                   //WORK FINISHED
          }

    },
    smallFunction : function(index){

    // Do Stuff!

    }

我在这里所做的只是检查下一个索引是否可以被 250 整除,如果是,那么我们使用超时来允许主 UI 线程更新。如果没有,我们直接再次调用它。问题解决了!

4

4 回答 4

2

这是从我写的早期答案修改的一些批处理代码:

var n = 0,
    max = data.length;
    batch = 100;

(function nextBatch() {
    for (var i = 0; i < batch && n < max; ++i, ++n) {
        myFunc(n);
    }
    if (n < max) {
        setTimeout(nextBatch, 0);
    }
})();
于 2013-05-03T07:15:45.257 回答
2

实际上 1500 次超时没什么,所以你可以简单地这样做:

var i1 = 0
for (var i = 0; i < 1500; i++) setTimeout(function() { doSomething(i1++) }, 0)

系统会为您排队超时事件,它们将立即一个接一个地被调用。如果用户在执行过程中点击任何东西,他们不会注意到任何延迟。并且没有“脚本运行时间过长”的事情。

根据我的实验,V8 每秒可以创建 500,000 次超时。

更新

如果您需要i1传递给您的工作函数,只需传递一个对象,并在函数内部增加计数器。

function doSomething(obj) {
   obj.count++
   ...put actual code here
}
var obj = {count: 0}
for (var i = 0; i < 1500; i++) setTimeout(function() { doSomething(obj) }, 0)

在 Node.js 下,您还可以使用setImmediate(...).

于 2013-05-03T08:07:14.880 回答
0

有更好的吗?

如果你认为它只在现代浏览器中工作没问题——那么你应该研究一下“Web Workers”,它可以让你在后台执行 JS。

https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers

于 2013-05-03T08:17:41.737 回答
0

你可能想用它requestAnimationFrame来分解你的执行。以下是developers.google.com 文章Optimize JavaScript Execution中的一个示例,如果任务快于X 毫秒,他们甚至会一次进行几次迭代。

var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
  var taskFinishTime;

  do {
    // Assume the next task is pushed onto a stack.
    var nextTask = taskList.pop();

    // Process nextTask.
    processTask(nextTask);

    // Go again if there’s enough time to do the next task.
    taskFinishTime = window.performance.now();
  } while (taskFinishTime - taskStartTime < 3);

  if (taskList.length > 0)
    requestAnimationFrame(processTaskList);

}
于 2021-12-10T12:25:13.457 回答