Optimize your Javascript cross-browser code through branching

Dustin Diaz recently published an article outlining seven Javascript techniques that you'd be wise to start using in your scripts. It's a well written article and you should definitely give it read if you haven't already. Whether you're just beginning with Javascript or you consider yourself an expert, Dustin's article surely has something for you.

One concept he writes about that I particularly like is that of "branching." I've never actually heard it called that before, but it's a very sound concept and one that I doubt is being utilized often enough by today's Javascripters.

What is branching?

Put simply, branching (in Javascript) is the use of closures to get the browser to interpret a block of code only one time, when otherwise, it would interpret that block over and over and over again (presumably, each time you call your function).

Why would you want to do this? Because, when it comes to cross-browser code, chances are you have a lot object detection code whose primary purpose is to determine if the browser supports a particular feature/property, like this:

function addEvent(el, evt, fn)
{
   if (typeof elem.addEventListener !== 'undefined')
   {
      // w3c event wire up here
   }
   else if (typeof elem.attachEvent !== 'undefined')
   {
      // ie event wire up here...
   }
}

The above is a very simplified version of a cross-browser addEvent(..) function that takes care of (some of) the differences in event models between IE and W3C browsers. This is all well and good, but consider what happens when I do the following:

var elems = document.getElementsByTagName('div');     // assume 5000 <div> elements returned
var i, len = elems.length;
for (i = 0; i < len; i++)
{
   addEvent(elems[i], 'click', function() { alert('Hi there!'); });
}

Five thousand times the browser is checking: if (typeof elem.addEventListener !== 'undefined')... -- once for each element in the elems array! Gosh, that feels like a waste, doesn't it? Wouldn't it be good if we could somehow tell the browser: Hey browser, once you've figured out that .addEventListener(..) is supported, don't bother checking again for the next 4999 iterations!?

It turns out you can achieve something like this, through the use of branching.

Branching in action

In a sense, branching is a lot like conditional compilation. Similarly to the way you can use #ifdef directives to instruct your C-style programs to conditionally compile certain blocks of code into your executable, branching allows you to conditionally "compile" (as it were) certain blocks of code into your Javascript.

This is accomplished by creating separate functions that contain your cross-browser logic and then wrapping them up inside of another function, which will return the appropriate function when executed. The nifty part though, is that this outer function will only be executed one time (that's the sort of "conditional compilation" part).

An example should help illustrate this:

var addEvent = function()
{
   function ie_addEvent(el, evt, fn)
   {
      el.attachEvent('on' + evt, fn);
   }

   function w3c_addEvent(el, evt, fn, useCap)
   {
      el.addEventListener(evt, fn, useCap);
   }

   if (typeof window.addEventListener !== 'undefined')
   {
      return w3c_addEvent;
   }
   else if (typeof window.attachEvent !== 'undefined')
   {
      return ie_addEvent;
   }
}();   // <= This is the critical part!

In the preceding example, I've separated out the logic for IE and W3C browsers into two separate functions. Those functions have been declared within the scope of the addEvent(), so they're not accessible from outside.

The most critical thing to notice though, is the use of }(); at the very end of the code. It's important to realize what's happening here. We're declaring a function, addEvent, with the code: var addEvent = function() {, and then we're immediately executing that function at the end of its declaration with }();.

What it means for you

This means that as the Javascript interpreter is parsing your code, it will execute the addEvent() function right away, whose logic instructs the browser return one of two functions (but not both)!

The net effect of this is that addEvent will be equal to ie_addEvent when IE is being used, and it will be equal to w3c_addEvent when W3C browsers are being used. In either case, the browser only needed to execute the if-conditional one time. Beautiful!

You can prove to yourself that this works, but simply executing: alert(addEvent.toString()) in both IE and in a W3C browser. Notice that you'll get the proper function for each browser, but that neither function includes any if (typeof blah_blah_blah)... checks.

Conclusion

It's no surprise that this is going to be much more efficient than the original addEvent(..) function, which did not utilize branching. After all, it's 4,999 fewer if (typeof ...) checks for the browser to execute!

This concept isn't necessarily limited to cross-browser code either. Branching can be useful in lots of situations. As always, I'd recommend analyzing your particular situation and determining if branching is going to beneficial for you (chances are, it will).

If you haven't already, be sure to check out Dustin's article. Besides branching, he covers six other useful techniques that every Javascripter should be aware of.

Comments welcome.

3 Responses

  1. Nimrod Talmon Says:

    ThanX a lot,
    I am enjoying reading your blog,
    full with great stuff
    explained beautifully…

  2. sstchur Says:

    Thanks! Check back often; I do my best to keep it up to date with all kinds of web/javascripty goodness :-)

  3. Md. Mahabubul Alam Says:

    this is realy nice … i like this topic very much… ever i have seen it was the best…

Got something to say?

Please note: Constructive criticism is welcome. Rude or vulgar comments however, are not and will be removed during moderation.