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 my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




30 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
[...] I did a lot of research on performance, resulting in the Speed Up Your JavaScript blog post series (part 1, part 2, part 3, part 4) as well as several talks, namely JavaScript Variable Performance at the [...]
Announcing High Performance JavaScript | NCZOnline on February 9th, 2010 at 9:01 am
[...] you want to take loop performance to the next level, Zakas also provides a more advanced loop optimization technique, which runs through the loop asynchronously (so [...]
10 Javascript Performance Boosting Tips from Nicholas Zakas | Jon Raasch's Blog on February 10th, 2010 at 12:56 pm
“Perform node operations out of the DOM” – since they cause reflows
However, IE, has a dom insertion order leak pattern (http://msdn.microsoft.com/en-us/library/bb250448(VS.85).aspx). My tests find that if I have a dynamically created node, set innerHTML to it and then append it to the DOM, it leaks memory! But if I append it to the dom and set innerHTML, and then remove it, it recovers the memory. How can I get around it?
Ajay Nair on May 1st, 2010 at 3:03 am
With regards to the first comment to this topic (yeah, old, I know), browsers are implementing the Web Workers API, which is introduces a true threaded JS engine. Of course, for the reasons posted, it doesn’t mess with the DOM, but it does reduce or eliminate the need for timers.
It would be a nice topic for another blog post.
Bob on May 27th, 2010 at 6:06 pm
[...] your code, it pretty much has to be done by you.”Series of articles by the author on the same topic:Speed up your JavaScript – Part 1Speed up your JavaScript – Part 2Speed up your JavaScript – Part 3Speed up your JavaScript – Part [...]
Seven Must-See Videos and Presentations for Web App Developers - Smashing Magazine on July 18th, 2010 at 3:57 am
[...] 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 on July 26th, 2010 at 11:14 pm
Comments are automatically closed after 14 days.