Treehouse

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?

ydkjs-2.png

Recent Features

Incredible Demos

  • LightFace:  Facebook Lightbox for&nbsp;MooTools

    One of the web components I've always loved has been Facebook's modal dialog.  This "lightbox" isn't like others:  no dark overlay, no obnoxious animating to size, and it doesn't try to do "too much."  With Facebook's dialog in mind, I've created LightFace:  a Facebook lightbox...

  • Full Width&nbsp;Textareas

    Working with textarea widths can be painful if you want the textarea to span 100% width.  Why painful?  Because if the textarea's containing element has padding, your "width:100%" textarea will likely stretch outside of the parent container -- a frustrating prospect to say the least.  Luckily...

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!