Found Code: Optimizing Large Form Performance in JavaScript
Disclaimer: Check the date! This post was written way back in . If there are technical things in here, they're more than likely outdated, or there may be better ways to do them by now. You've been warned.
As I’ve covered before, ill-used JavaScript can lead to some serious performance problems, most of which are caused by simply not thinking about what the code is really doing. Recently I came across a site that provided digital photo printing, This site had a nice interface that allowed my to upload close to three hundred photos. On the resulting page, each photo was displayed with all of the available sizes as input boxes, which looked something like this. I liked the interface, but came across a very serious problem. The event handlers that updated the totals box ran on the keyup event and recalculated the total of the entire form! This worked fine with ten or twenty photos, but the 300 that I provided brought my browser to a screeching halt.
I’ve taken the liberty of creating a very simplistic mock-up of the form and a simplified version of the JavaScript, which is available in my examples section. The demo uses Firebug and Firebug Lite for logging just like I did in my dollar function article, and the benchmark class from that article as well. The site’s JavaScript was a bit more complex and actually did an AJAX lookup of the price on each keyup, but I’m more concerned with the JavaScript performance here, so I simplified the code to something like this:
Basically, on window load, this code grabs every input element, sets its value to zero, and binds an event handler to it.
The event handler runs on key up and loops through every input box in the “pictures” list, and updates the totals inputs
at the top of the page. As I said above, this code works fine with 20 pictures,
but it starts getting slow around 300, and becomes
almost unusable at 1000. Care to
try 10,000? (Be careful, it crashes my browser!)
To test it, simply enter values in the photo inputs and watch the totals boxes increment.
The main problem with this code comes from the recalculate
function. Problem number one is my personal pet peeve,
the dollar sign function is called at least six times! Well, I guess six times wouldn’t be terrible for the entire page, but
it’s called at least six times on every key up event! Problem number two, the biggest problem, is the fact that this code
re-crawls what amounts to the entire DOM every time the event fires. Obviously the larger the DOM, the more time this is
going to take.
So, how do we fix it? Well, here’s how I fixed it, I’ll explain the details below:
To solve problem number one from above I created a simple object for storing references to all of the total input boxes (lines 9-14),
now we have a simple associative array lookup whenever we need to update a total. Problem number two is mainly solved by recording
the original value of the input on focus (line 22, 31-35), and then comparing them on blur (line 23, 37-54). Because we’re doing
this on blur, we can update only the necessary total input (lines 45-49) instead of recalculating the entire form. I made one final
tweak, mainly to make solving problem number one easier, and that is the recalculate
function now returns a specific
event handler for the given input so that the event handler itself does not need to call the dollar function.
So, comparing these in my regular, not-very-scientific fashion, I came up with the following results. I chose to measure the startup time, which will increase with the size of the page, as well as the event handler time. I also measured these times across a pretty decent amount of pictures, and across a few browsers.
Safari (OS X)
Optimized Time | Unoptimized Time | |||
---|---|---|---|---|
Pictures | Load (ms) | Handler (ms) | Load (ms) | Handler (ms) |
10 | 4 | 0 | 6 | 3 |
50 | 17 | 0 | 14 | 13 |
100 | 33 | 0 | 24 | 26 |
1000 | 365 | 0 | 452 | 178 |
Internet Explorer 7 (Windows Vista)
Optimized Time | Unoptimized Time | |||
---|---|---|---|---|
Pictures | Load (ms) | Handler (ms) | Load (ms) | Handler (ms) |
10 | 56 | 0 | 48 | 9 |
50 | 238 | 0 | 213 | 76 |
100 | 457 | 0 | 424 | 235 |
1000 | 4642 | 0 | 4584 | 28110 |
28 seconds!? Why!?
Firefox 2 (Windows Vista)
Optimized Time | Unoptimized Time | |||
---|---|---|---|---|
Pictures | Load (ms) | Handler (ms) | Load (ms) | Handler (ms) |
10 | 12 | 0 | 8 | 7 |
50 | 45 | 0 | 31 | 30 |
100 | 87 | 1 | 60 | 59 |
1000 | 985 | 3 | 584 | 581 |
The results pretty obviously speak for themselves, but there is one caveat, be sure to notice the initial load time. Since the event handlers still need to be assigned to each input on the page the more inputs there are the longer the page load takes, and the load time is even slightly slower on the optimized page. Be sure to consider this time, possibly by capping the number of inputs displayed, since the code itself is very processor intensive and appears to actually hang the entire computer while processing. Obviously, these fixes become more important as the number of inputs grows, but any speed increase when the user is directly interacting with the page is a good one!