Speed up your JavaScript, Part 1
In my last post, I talked about the conditions under which the dreaded long-running script dialog is displayed in browsers. Browsers will stop executing script either when they’ve executed too many statements (Internet Explorer) or when the JavaScript engine has been running for a specific amount of time (others). The problem, of course, isn’t the way that the browser is detecting long-running scripts, it’s that the script is taking too long to execute.
There are four main reasons why a script can take too long to execute:
- Too much happening in a loop.
- Too much happening in a function.
- Too much recursion.
- Too much DOM interaction.
In this post, I’m going to focus on the first issue: too much happening in a loop. Loop iterations happen synchronously, so the amount of time it takes to fully execute the loop is directly related to the number of iterations. There are, therefore, two situations that cause loops to run too long and lock up the browser. The first is that the loop body is doing too much for each iteration and the second is that the loop is running too many times. These can cause the browser to lock up and display the long-running script warning.
The secret to unraveling this problem is to evaluate the loop to answer two questions:
- Does the loop have to execute synchronously?
- Does the order in which the loop’s data is processed matter?
If the answer to both of these questions is “no,” then you have some options for splitting up the work done in the loop. The key is to examine the code closely to answer these questions. A typical loop looks like this:
for(var i=0; i < items.length; i++){
process(items[i]);
}
This doesn’t look too bad though may take very long depending on the amount of time necessary to run the process() function. If there’s no code immediately after the loop that depends on the results of the loop executing, then the answer to the first question is “no.” You can clearly see that each iteration through the loop isn’t dependent on the previous iteration because it’s just dealing with one value at a time, so the answer to the second question is “no.” That means the loop can be split in a way that can free up the browser and avoid long-running script warnings.
In Professional JavaScript, Second Edition, I introduce the following function as a way to deal with loops that may take a significant amount of time to execute:
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
The chunk() function is designed to process an array in small chunks (hence the name), and accepts three arguments: a “to do” list of items, the function to process each item, and an optional context variable for setting the value of this within the process() function. A timer is used to delay the processing of each item (100ms in this case, but feel free to alter for your specific use). Each time through, the first item in the array is removed and passed to the process() function. If there’s still items left to process, another timer is used to repeat the process. The loop described earlier can be rewritten to use this function:
chunk(items, process);
Note that the array is used as a queue and so is changed each time through the loop. If you want to maintain the array’s original state, there are two options. First, you can use the concat() method to clone the array before passing it into the function:
chunk(items.concat(), process);
The second option is to change the chunk() function to do this automatically:
function chunk(array, process, context){
var items = array.concat(); //clone the array
setTimeout(function(){
var item = items.shift();
process.call(context, item);
if (items.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
Note that this approach is safer than just saving an index and moving through the exist array, since the contents of the array that was passed in may change before the next timer is run.
The chunk() method presented here is just a starting point for how to deal with loop performance. You can certainly change it to provide more features, for instance, a callback method to execute when all items have been processed. Regardless of the changes you may or may not need to make to the function, it is a general pattern that can help optimize array processing to avoid long-running script warnings.
Translations
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of Yahoo!, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
You can leave a response, or trackback from your own site.




24 Comments
Don’t you think, though, that having to take perfectly normal code and mangle it to jump through browser hoops is not a sign that something is wrong with your code (although some people’s loops do need to be cut down) but that there is something inherently wrong with the JS engine of that browser in that it can’t complete simple things in a short time-frame?
Maybe I’m a little crazy but why do browsers handle JS synchronously? i.e., Why do they stop what they’re doing to run your loop and then continue?
Do you know of any threaded JS engines for web browsers?
John Rockefeller on January 19th, 2009 at 4:20 pm
I completely agree. There are a lot of times when the issue is poorly written code. I have, however, come across times when I needed to do a lot of array processing to get something to work. This technique helped a great deal.
JavaScript really has no choice but to run synchronously because it can update the DOM, and thus, affect the appearance of the page. Imagine the trouble we’d get into if two different threads could each set the left coordinate for a DOM element at the same time!
Nicholas C. Zakas on January 19th, 2009 at 4:24 pm
I found your article through an entry on Ajaxian that also linked to me.
Coincidentally, the same day you posted this article, I released an update to a library that takes a different approach to the same problem:
http://www.tumuski.com/code/clumpy/overview/
Thomas Peri on January 19th, 2009 at 5:43 pm
[...] week, I covered the first reason why JavaScript can take too long to execute: too much happening in a loop. There’s a similar problem with functions in that sometimes they’re just doing too [...]
Speed up your JavaScript, Part 2 | NCZOnline on January 20th, 2009 at 11:08 am
[...] Speed up your JavaScript, Part 1 [...]
網站製作學習誌 » [Web] 連結分享 on January 22nd, 2009 at 6:27 am
@John Rockefeller:
My understanding is that JS runs synchronously because it doesn’t know what might happen within a loop that might affect what the /next/ loop might do.
Atg
Aaron T Grogg on January 22nd, 2009 at 4:54 pm
Speed up your JavaScript, Part 1…
Thank you for submitting this cool story - Trackback from DotNetShoutout…
DotNetShoutout on January 24th, 2009 at 12:37 am
[...] Speed up your Javascript part 1 [...]
Speed up your Javascript - Code Couch on January 26th, 2009 at 8:31 am
[...] past few weeks, I’ve been exploring the various techniques for speeding up your JavaScript. Part 1 covered how to deal with loops that are doing too much. Part 2 focused on functions that do too [...]
Speed up your JavaScript, Part 4 | NCZOnline on February 3rd, 2009 at 9:01 am
[...] I good series of articles over at nczonline.net Speed up your JavaScript, Part 1 | NCZOnline. [...]
My Bad Attitude » Speed up your JavaScript on February 5th, 2009 at 12:23 pm
would it be possible or prudent to create an iframe and load the intensive javascript in it?
then maybe have the procedure/function issue some kind of call on completion?
mat on February 5th, 2009 at 12:32 pm
Thank you, Nicholas.
For anyone using this to replace jQuery’s $.each(arr,function(i,v){…}), if your process function needs to access the iterator value (i), this might help:
function chunk(array, process, context){
var i=0;
setTimeout(function(){
var item = array.shift();
process.call(context,i,item);
i++;
if (array.length > 0){
setTimeout(arguments.callee, 20);
}
}, 100);
}
Raymond Ie on February 6th, 2009 at 3:20 pm
Why not use Duff’s device? See: http://www.websiteoptimization.com/speed/10/10-18.txt
Renzo Kooi on February 10th, 2009 at 3:33 am
@Renzo - Duff’s device may speed up array processing in some cases, but it can still cause the long-running script dialog to appear.
Nicholas C. Zakas on February 10th, 2009 at 2:44 pm
@Nicholas: you’re right, I recently experienced it, testing insertion of a huge bunch of new divs in the DOM tree. Thanks for your writings by the way.
Renzo Kooi on March 11th, 2009 at 5:06 am
[...] 【原文标题】Speed up your JavaScript, Part 1【原文作者】Nicholas C. [...]
如何提升JavaScript的运行速度(循环篇) « 七月佑安 on March 15th, 2009 at 7:10 am
[...] present themselves. I started out by discussing the long-running script dialog and then moved on to other performance issues. I thought I had covered most of the annoying and ill-explained JavaScript [...]
JavaScript stack overflow error | NCZOnline on May 19th, 2009 at 9:02 am
[...] 【原文标题】Speed up your JavaScript, Part 1 【原文作者】Nicholas C. Zakas [...]
如何提升JavaScript的运行速度(循环篇) « iUE on June 3rd, 2009 at 11:26 am
[...] if I would come and share some tips along those lines to the folks at Google. In following along my series of blog posts about JavaScript performance, I entitled this talk, Speed up your [...]
Speed up your JavaScript: The talk | NCZOnline on June 5th, 2009 at 2:05 am
[...] 在过去的几周中,我为大家介绍了几种可以加快JavaScript脚本运行速度的技术。第一节介绍了如何优化循环。第二节的重点放在优化函数内部代码上,还介绍了队列(queuing)和记忆化(memoization)两种技术,来减轻函数的工作负担。第三节就如何将递归转换为迭代循环或者记忆化方式的话题,展开了讨论。第四节是这个系列的最后一篇,也就是本文,将重点阐述过多的DOM操作所带来的影响。 [...]
speed-up-your-javascript-part-4 « Oragg.com on June 14th, 2009 at 10:45 am
[...] 上周我在《too much happening in a loop》(译文)这篇文章中介绍了JavaScript运行时间过长的第一个原因。相似的情况有时也出现在函数的定义上,函数也可能因为使用不当而过载使用。通常情况是函数内包含了过多的循环(不是在循环中执行了过多的内容),太多的递归,或者只不过是太多不相干但又要一起执行的操作。 [...]
如何提升JavaScript的运行速度(函数篇) « 七月佑安 on June 14th, 2009 at 1:40 pm
[...] 本文译自Nicholas C. Zakas于2009年1月13日在个人网站上发表的《Speed up your JavaScript, Part 1》。原文是唯一的正式版,本文是经过原文作者授权的简体中文翻译版。译者在翻译的准确性上做了大量的努力,并承诺译文的内容完全忠于原文,但可能还是包含疏漏和不妥之处,欢迎大家指正。译序和译注的内容是非正式的,仅代表译者个人观点。 [...]
如何提升JavaScript的运行速度(循环篇) « 七月佑安 on June 14th, 2009 at 1:49 pm
[...] too long ago, I blogged about a way to asynchronously process JavaScript arrays to avoid locking up the browser (and [...]
Timed array processing in JavaScript | NCZOnline on August 11th, 2009 at 9:01 am
[...] Speed up your JavaScript, Part 1 [...]
Neil Skoglund » Blog Archive » 120 Tips, Tricks, and Tuts from 2009 Worth your Time on December 28th, 2009 at 10:21 pm
Leave a Comment