在 Internet Explorer 中禁用长时间运行的脚本消息

IT技术 javascript internet-explorer
2021-01-27 22:50:50

我有一个 JavaScript 函数,它包含一个迭代了很多次的 for 循环。
调用该函数后,IE浏览器显示如下信息:

停止运行此脚本?
此页面上的脚本导致您的 Web 浏览器运行缓慢。如果它继续运行,您的计算机可能会变得无响应。

我怎样才能解决这个问题?
无论如何我可以从 IE 中禁用此消息吗?

4个回答

当 Internet Explorer 达到一段 JavaScript 的最大同步指令数时,将显示此消息。默认最大值为 5,000,000 条指令,您可以通过编辑注册表在单台机器上增加此数量

Internet Explorer 现在会跟踪已执行脚本语句的总数,并在每次启动新脚本执行时重置该值,例如从超时或事件处理程序开始,用于使用脚本引擎的当前页面。当该值超过阈值时,Internet Explorer 会显示一个“长时间运行的脚本”对话框。

为可能正在查看您的页面的所有用户解决问题的唯一方法是使用计时器分解循环执行的迭代次数,或者重构您的代码以便它不需要处理尽可能多的指令。

用定时器打破循环相对简单:

var i=0;
(function () {
    for (; i < 6000000; i++) {
        /*
            Normal processing here
        */

        // Every 100,000 iterations, take a break
        if ( i > 0 && i % 100000 == 0) {
            // Manually increment `i` because we break
            i++;
            // Set a timer for the next iteration 
            window.setTimeout(arguments.callee);
            break;
        }
    }
})();
@AndyE - 我通常不包括参数,但我看到 jQuery 在使用 0 和 1ms 延迟之间交换,因为我不明白的某种原因(可能向后兼容某些东西)但它从未被忽略,所以我模仿时我正在提供建议。我确实知道几年前 jQuery 对动画帧有 13 毫秒的硬编码超时,这有点难以解决。也许这个下限就是你的想法?或者,也许计算机过去只是较慢。
2021-03-14 22:50:50
@DavidMeister,该链接已失效
2021-03-14 22:50:50
@AndyE - 我在圣诞节假期做了一些更多的实验,并就这个主题撰写了一些文章,比直接发布到 Stack Overflow 更深入一些。以为你可能感兴趣,thedavidmeister.info/post/...
2021-03-31 22:50:50
非常感谢您链接到注册表黑客。我一直在尝试在 VM 上运行几个性能测试,但 IE 弹出对话框破坏了结果。我花了很长时间找到这个问题,但希望通过添加此评论并提及几个关键字(例如“停止运行此脚本?”和“IE 弹出对话框”),它会在谷歌中显示得更高。
2021-04-08 22:50:50
如果你没有给 setTimeout 一个明确的时间,即。跳过第二个参数,这个脚本会运行得更快,你会得到同样的效果。不提供第二个参数会告诉浏览器“尽快”而不是“至少在这么多时间之后”触发传递的函数。
2021-04-10 22:50:50

无响应脚本对话框显示某些 javascript 线程耗时太长太完整。编辑注册表可以工作,但您必须在所有客户端计算机上执行此操作。您可以使用如下的“递归闭包”来缓解这个问题。它只是一种编码结构,它允许您长时间运行 for 循环并将其更改为可以执行某些工作的内容,并跟踪它停止的地方,让步给浏览器,然后从停止的地方继续,直到我们完成。

图 1,将此实用程序类 RepeatingOperation 添加到您的 javascript 文件中。您无需更改此代码:

RepeatingOperation = function(op, yieldEveryIteration) {

  //keeps count of how many times we have run heavytask() 
  //before we need to temporally check back with the browser.
  var count = 0;   

  this.step = function() {

    //Each time we run heavytask(), increment the count. When count
    //is bigger than the yieldEveryIteration limit, pass control back 
    //to browser and instruct the browser to immediately call op() so
    //we can pick up where we left off.  Repeat until we are done.
    if (++count >= yieldEveryIteration) {
      count = 0;

      //pass control back to the browser, and in 1 millisecond, 
      //have the browser call the op() function.  
      setTimeout(function() { op(); }, 1, [])

      //The following return statement halts this thread, it gives 
      //the browser a sigh of relief, your long-running javascript
      //loop has ended (even though technically we havn't yet).
      //The browser decides there is no need to alarm the user of
      //an unresponsive javascript process.
      return;
      }
    op();
  };
};

图 2,以下代码表示导致“停止运行此脚本”对话框的代码,因为它需要很长时间才能完成:

process10000HeavyTasks = function() {
  var len = 10000;  
  for (var i = len - 1; i >= 0; i--) {
    heavytask();   //heavytask() can be run about 20  times before
                   //an 'unresponsive script' dialog appears.
                   //If heavytask() is run more than 20 times in one
                   //javascript thread, the browser informs the user that
                   //an unresponsive script needs to be dealt with.  

                   //This is where we need to terminate this long running
                   //thread, instruct the browser not to panic on an unresponsive
                   //script, and tell it to call us right back to pick up
                   //where we left off.
  }
}

图 3. 以下代码修复了图 2 中存在问题的代码。请注意 for 循环被替换为递归闭包,该闭包每 10 次重载任务()迭代将控制权返回给浏览器

process10000HeavyTasks = function() {

  var global_i = 10000; //initialize your 'for loop stepper' (i) here.

  var repeater = new this.RepeatingOperation(function() {

    heavytask();

    if (--global_i >= 0){     //Your for loop conditional goes here.
      repeater.step();        //while we still have items to process,
                              //run the next iteration of the loop.
    }
    else {
       alert("we are done");  //when this line runs, the for loop is complete.
    }
  }, 10);                   //10 means process 10 heavytask(), then
                            //yield back to the browser, and have the
                            //browser call us right back.

  repeater.step();          //this command kicks off the recursive closure.

};

改编自此来源:

http://www.picnet.com.au/blogs/Guido/post/2010/03/04/How-to-prevent-Stop-running-this-script-message-in-browsers

我会争辩说你不想这样做,因为 RepeatingOperation 可能只被调用 1000 次,并且由于 setTimeout() 上的硬编码最小延迟导致站点无响应的时间至少为 4-10 秒。请参阅安迪 E 回答的评论线程。
2021-03-31 22:50:50

就我而言,在播放视频时,每次currentTime视频更新时我都需要调用一个函数所以我使用timeupdate了视频事件,我开始知道它每秒至少被触发 4 次(取决于您使用的浏览器,请参阅)。所以我将它更改为每秒调用一个函数,如下所示:

var currentIntTime = 0;

var someFunction = function() {
    currentIntTime++;
    // Do something here
} 
vidEl.on('timeupdate', function(){
    if(parseInt(vidEl.currentTime) > currentIntTime) {
        someFunction();
    }
});

someFunc至少1/3可以减少对 的调用,并且可以帮助您的浏览器正常运行。它对我有用!!!

我无法对以前的答案发表评论,因为我还没有尝试过。但是我知道以下策略对我有用。它不太优雅,但可以完成工作。它也不需要像其他一些方法似乎做的那样将代码分解成块。就我而言,这不是一个选项,因为我的代码对正在循环的逻辑进行了递归调用;即,没有实用的方法可以跳出循环,然后能够通过使用全局变量以某种方式恢复以保留当前状态,因为这些全局变量可以通过在随后的递归调用中引用它们来更改。所以我需要一种不会让代码有机会破坏数据状态完整性的直接方法。

假设“停止脚本?” 在多次迭代(在我的情况下,大约 8-10 次)之后,在 for() 循环执行期间出现对话框,并且弄乱注册表是没有选择的,这里是修复程序(无论如何,对我来说):

var anarray = [];
var array_member = null;
var counter = 0; // Could also be initialized to the max desired value you want, if
                 // planning on counting downward.

function func_a()
{
 // some code
 // optionally, set 'counter' to some desired value.
 ...
 anarray = { populate array with objects to be processed that would have been
             processed by a for() }
 // 'anarry' is going to be reduced in size iteratively.  Therefore, if you need
 //  to maintain an orig. copy of it, create one, something like 'anarraycopy'.
 //  If you need only a shallow copy, use 'anarraycopy = anarray.slice(0);'
 //  A deep copy, depending on what kind of objects you have in the array, may be
 //  necessary.  The strategy for a deep copy will vary and is not discussed here.
 //  If you need merely to record the array's orig. size, set a local or
 //  global var equal to 'anarray.length;', depending on your needs.
 // - or -
 // plan to use 'counter' as if it was 'i' in a for(), as in
 // for(i=0; i < x; i++ {...}

   ...

   // Using 50 for example only.  Could be 100, etc. Good practice is to pick something
   // other than 0 due to Javascript engine processing; a 0 value is all but useless
   // since it takes time for Javascript to do anything. 50 seems to be good value to
   // use. It could be though that what value to use does  depend on how much time it
   // takes the code in func_c() to execute, so some profiling and knowing what the 
   // most likely deployed user base is going to be using might help. At the same 
   // time, this may make no difference.  Not entirely sure myself.  Also, 
   // using "'func_b()'" instead of just "func_b()" is critical.  I've found that the
   // callback will not occur unless you have the function in single-quotes.

   setTimeout('func_b()', 50);

  //  No more code after this.  function func_a() is now done.  It's important not to
  //  put any more code in after this point since setTimeout() does not act like
  //  Thread.sleep() in Java.  Processing just continues, and that is the problem
  //  you're trying to get around.

} // func_a()


function func_b()
{
 if( anarray.length == 0 )
 {
   // possibly do something here, relevant to your purposes
   return;
 }
//  -or- 
if( counter == x ) // 'x' is some value you want to go to.  It'll likely either
                   // be 0 (when counting down) or the max desired value you
                   // have for x if counting upward.
{
  // possibly do something here, relevant to your purposes
  return;
}

array_member = anarray[0];
anarray.splice(0,1); // Reduces 'anarray' by one member, the one at anarray[0].
                     // The one that was at anarray[1] is now at
                     // anarray[0] so will be used at the next iteration of func_b().

func_c();

setTimeout('func_b()', 50);

} // func_b()


function func_c()
{
  counter++; // If not using 'anarray'.  Possibly you would use
             // 'counter--' if you set 'counter' to the highest value
             // desired and are working your way backwards.

  // Here is where you have the code that would have been executed
  // in the for() loop.  Breaking out of it or doing a 'continue'
  // equivalent can be done with using 'return;' or canceling 
  // processing entirely can be done by setting a global var
  // to indicate the process is cancelled, then doing a 'return;', as in
  // 'bCancelOut = true; return;'.  Then in func_b() you would be evaluating
  // bCancelOut at the top to see if it was true.  If so, you'd just exit from
  // func_b() with a 'return;'

} // func_c()