Slow jQuery And The Thing About Duct Tape

So, here’s the thing about duct tape.

 

Ducttape_thumb

It is super useful. But if you’re working quickly, and with enough of it, your grip will inevitably fail and you’ll be left helpless except to watch as its sticky side makes irrevocable contact with itself. And if your forearm hair was involved in the exchange, well. There goes your will to carry on.

What I mean to get at with all the jabber about our ubiquitous silvery adhesive friend is this: jQuery is arguably the duct tape of WebForms development, and its quick-fix powers over rendered markup are seemingly without limit. Use enough of it at once, though, and it will find ways of getting stuck to itself, or indeed to the hirsute wrist of your DOM. As your page’s once-blinding responsiveness disappears over the usability horizon, you shed a single caffeinated tear, and it dawns on you that getting this untangled could be a slow and painful affair. Perhaps involving plenty of unintentional hair loss.

Now, let me brush away that tear—in a totally non-creepy, platonic sort of way—as I submit for you today some tricks to get your jQuery just about as unstuck and performant as an abstraction over an interpreted language could ever hope to be.

Step Zero

In any performance tuning venture, step zero is to measure the problem. This kills two sluggish birds with one stone. First, it calls your attention to the sucking chest wound in your code, so that you don’t waste time kissing the boo-boo on its wittle finger. Second, it gives you a baseline to compare against your theoretically optimized code later.

Internet Explorer 9 comes standard with a perfectly decent performance profiler, so I’ll be rocking that for our purposes here. If, however, you scribble your hopes and dreams in a Field Notes and you’re allergic to IE, feel free to follow along using similar tooling for Chrome, Lynx, or whatever you prefer to check your mess in.

Setting the user-agent zealot-bait aside, let’s browse to the page in question. Then press F12 to open the Developer Tools window, and switch to the Profiler tab. Click the Start Profiling button, and reload the page.

start-profiling

At this point, if it’s only page load time you are concerned with, click Stop Profiling. Otherwise, do the thing on the page that seems to be angering the JavaScript gods, then click Stop Profiling. In a few seconds, a report will be generated showing which functions have flitted in and out of existence, and which ones should maybe think about taking the stairs every once in while.

A closer inspection of the Current View dropdown reveals that we get two ways to see this information: as a straight list of functions, or those same functions arranged on a call tree. Since what we seek is the path of greatest sadness through our code, we’ll select the Call Tree option, and then sort the Inclusive Time column, descending.

Once our perf data is presented this way, we simply start at the top, popping open functions on the tree. When we find a node corresponding to code over which we have any control (as opposed to jQuery itself, a 3rd-party plugin, or other framework code), we can finally let the healing begin.

call-tree

Get Better Selectors

After identifying our less-than-spritely blocks of code, the first thing we should examine is how its selectors are sculpted. If a selector is defined too broadly, without scope, it can bring your page to a grinding halt as the entire document (or way too much of it) is scanned for matches. For instance, a selector comprised of a single class:

// boo 
$(".row-status").removeClass("error");

To remedy this, narrow the breadth of the query using the id or (better yet) the path of ids that zeroes in on the relevant part of the page:

// yay
$("#content #grid .row-status").removeClass("error");

Alternatively, you could use the jQuery function’s second parameter to specify a context object:

// yay for me too
$(".row-status", $("#content #grid")).removeClass("error");

Remember to measure again after this tweak (and any of the others below) to track your progress. When code changes and functionality doesn’t, people on the team who are not you may get twitchy if you don’t have numbers ready to back it up.

More twitchy than usual, I mean.

j-Jà Vu

Another common selector faux pas to scan for is a situation where you are defining a selector inside of a loop, or the same selector is created multiple times. This leads to more cycles wasted on scanning the DOM for elements that were already reaped in a previous iteration.

// copy the important info to the parents of a lot of things

$('.a-lot-of-things').each(function () {

  $(this).parent().val( $('input.important-info').val() );

});

The fix for this is to consolidate—in the case of the loop, define the selector before the loop begins, so its work is done only once. For merely redundant selectors, stash the resulting object in a variable and use it inside the loop.

// copy the important info to the parents of a lot of things
// faster, more intense
var $info = $('input.important-info');
$('.a-lot-of-things').each(function () {
    $(this).parent().val( $info.val() );
});

Look For Each Each And Forgo It For A For

Another perf gain opportunity is any usage of jQuery’s each function. Sure, it’s an elegant alternative to a for loop, but depending on its contents, it may cost you. I have seen on the order of ten-fold speed gains just from converting each loops back to slightly less fancy for blocks:

// each
$('.a-lot-of-things').each(function (e) {
    $(this).val( $info.val() );
});
  
// for
var $things = $('.a-lot-of-things');
for(var i = 0; i < $things.length; i++)
{
  $($things[i]).val( $info.val() );
}

This one is a fine example of the tradeoff we sometimes find between beautiful and fast code. While the previous two fixes are merely implementing jQuery selector best practices, this one should only be implemented on an as-needed basis. A for loop is less readable and has more moving parts than an each call, making it harder to maintain.

Less delicately? I might as well beat my app with an ugly stick if I’m going to prematurely optimize my code.

With Great Abstraction Comes Great Responsibility

We’ve measured the damage, made our selectors more selective, stopped repeating ourselves, and applied precision hideousness to take the slack out of our ponderous page renders.

With frameworks like jQuery doing much of the heavy lifting for us, it sure is a snap to write client-side code quickly these days. But too often, when we write code fast, we don’t write fast code. Rapid development with jQuery is super useful—if you make the time to unstick what becomes stuck before you call it done.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s