JavaScript: Avoiding Repeated Work

By  on  

I love to scavenge the source code of the web's large sites, looking for patterns to both apply to my coding and to find hacks or techniques I'd not heard of before.  One problem I often find with the coding of large sites is repeated operations.  There are a few different mistakes I see when looking at their code and I want to share those with you so you can speed up your own JavaScript code.

Repeated Element Collection

The most problem I see most often is JavaScript code is repeated element collection.  Selector engines and querySelectorAll have gotten to be very fast but repeated work is always a slower than doing the work once.  Obviously the problem and solution look like:

// :(
$$(".items").addClass("hide");
	// ... and later ...
$$(".items").removeClass("hide");


//  :)
var items = $$(".items");
	// ... and use to heart's content from here on out!

Scolding developers for repeated element collection is a daily occurrence but this scolding needs to be reinforced.  Of course repeated element collection cannot always be avoided (think sites with AJAX page loads), but in those cases, you will most likely want to use event delegation instead of direct element retrieval and event application.

Repeated Conditionals

Repeated condition calculation is a common case but also a common pattern which can be avoided.  You will see something like this:

var performMiracle = function() {
	// If the browser has feature A, use it...
	if(features.someFeature) {

	}
	// ... if not, do another
	else {

	}
};

It works but it's not the most efficient use of code, and the conditional checks are run upon each call.  Instead, something like this would be better:

var performMiracle = features.someFeature ? function() {
	// Plan A stuff
} : function() {
	// Plan B stuff
};

Only one conditional and the method or variable is already set to the result of the conditional!

Repeated Object Creation

On repeated operation that goes under the radar is repeated object creation, usually in the form of a regular expression.  Consider the following:

function cleanText(dirty) {
	// Get rid of SCRIPT tags
	clean = dirty.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, "");

	// Do some more cleaning, maybe whitespace, etc.

	return clean;
}

The code above repeatedly creates a new (albeit the same) regular expression object -- an opportunity to save numerous object creations by creating the RegExp in a scope context than the function:

var scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
function cleanText(dirty) {
	// Get rid of SCRIPT tags
	clean = dirty.replace(scriptRegex, "");

	// Do some more cleaning, maybe whitespace, etc.

	return clean;
}

In the case above, the RegExp is only created once but used many times -- a nice save in processing.

Those are just a few of the issues I see often repeated when I browse JavaScript written by other developers. What other common mistakes do you see?

Recent Features

  • By
    9 Mind-Blowing Canvas Demos

    The <canvas> element has been a revelation for the visual experts among our ranks.  Canvas provides the means for incredible and efficient animations with the added bonus of no Flash; these developers can flash their awesome JavaScript skills instead.  Here are nine unbelievable canvas demos that...

  • By
    Write Better JavaScript with Promises

    You've probably heard the talk around the water cooler about how promises are the future. All of the cool kids are using them, but you don't see what makes them so special. Can't you just use a callback? What's the big deal? In this article, we'll...

Incredible Demos

  • By
    Record Text Selections Using MooTools or jQuery AJAX

    One technique I'm seeing more and more these days (CNNSI.com, for example) is AJAX recording of selected text. It makes sense -- if you detect users selecting the terms over and over again, you can probably assume your visitors are searching that term on Google...

  • By
    Image Data URIs with PHP

    If you troll page markup like me, you've no doubt seen the use of data URI's within image src attributes. Instead of providing a traditional address to the image, the image file data is base64-encoded and stuffed within the src attribute. Doing so saves...

Discussion

  1. Great tips David, Love your tutorials =)

  2. Jonathan Kingston

    It is a trade off with readability.

    I would only argue for using the regular expression version where code performance is important or where a different regular expression needs to be passed into the function at different times.

    In terms of DOM reuse that makes complete sense as you can make it more readable by naming what the selection is and also DOM selection is usually far more costly, having a reference to 1000 elements you have selected makes sense. Does a reference to a single regular expression?

    The repeated conditionals section actually does different things and again is much harder to read if those methods were long. Upon execution of those lines the performMiracle in the second example will always be whatever the conditionals parse to on first run, where as: performMiracle(), could mean different things several times depending on context. I have seen many bugs caused by conditional functions like this when race conditions happen that are then hard to fix without rewriting large portions of the code.

    Personally I would always take readability first, then flexibility and then performance. Only caching large searches through objects is something I do while writing my code for the first time.

    • It is a tradeoff with readability, but readability is felt by a few number of devs while speed/perf is felt by *every user*, thus I put the user first.

  3. Jebin

    Nice one @David.

  4. MaxArt

    I tend to avoid conditionals in iterations too, if they don’t depend of the iteration index or local variables. Instead of
    for (…) {
    if (…) …
    else …
    }
    I do
    if (…)
    for (…) …
    else
    for (…) …

    • MaxArt

      (By the way, what’s the point of wrapping code in pre tags if the result is still the same?)

  5. Nick

    Is the cost for creating short strings and checking features really noticeable? If it sacrifices readability, could that not fall under the case of premature optimization? Lower readability = more bugs and slower development. If the cost is negligible might be better to decide to focus on readability anf save time that can be spent on testing and optimzing the bigger performance problems

  6. I generally consider it good practice to have a cache object in any given module for storing selectors and strings. If you name the cached objects well, it actually becomes more readable. An example:

    var myModule = function () {
        var cache = {
            $menuButton: $(#menuButton),
            cleanerEx: "/]*>([\s\S]*?)/gi"
        };
    
        return {
    
            cleanText: function (dirty) {
    
                // Get rid of SCRIPT tags
                clean = dirty.replace(cache.cleanerEx, "");
    
                // Do some more cleaning, maybe whitespace, etc.
    
                return clean;
    
            }
        }
    };
    
  7. Luis Deleon

    It should be stressed more that Repeated Conditionals case is only ussable when features.someFeature doesn’t change, I know it is implied, I also know there is people who will not hesitate to use it on normal variables jeje

  8. Andrew

    > It is a tradeoff with readability, but readability is felt by a few number of devs while speed/perf is felt by *every user*, thus I put the user first.

    This sounds a bit dogmatic, doesn’t it? If your performance enhancement is too little to get noticed by anyone, the only persons who will feel more unreadable code are the devs (if they don’t introduce some bugs because the code isn’t readable). However, you also have the problem that you have to introduce more variables which are exposed to not related parts of your software. And if you have many regex functions, you also need to invent some clumsy variable names for each function’s regex expression which can get very unhandy.

    Nevertheless, you can use nested functions (currying) or the bind method to bind your variables to fix objects:

    var cleanText = function(regex) {
    					return function(dirty) {
    						clean = dirty.replace(regex, "");
    						return clean;
    					}
    			 }(/]*>([\s\S]*?)/gi);
    
    var cleanText = function(regex, dirty) {
    				clean = dirty.replace(regex, "");
    				return clean;
    			  }.bind(this, /]*>([\s\S]*?)/gi);
    

    By the way: Regex expressions are compiled the first time they are evaluated and therefore the object creation time is almost negligible.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!