Sunday, May 29, 2011

JavaScript is (like) a compiled language

False! JavaScript is not a compiled language.

However now that I get your attention, I want to introduce a programming technique that came (again, as my last post) from the great "Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries" (yes, I'm keep reading it).

The technique is introduced for tasks like producing functions that must be cross-browser, but the meaning behind is something more.

Function, Rewrite Thyself!

What can be the simplest way to obtain a cross-browser piece of code?
var crossBrowserMagicFunction = function() {
// some lines of code to know what browser we have
var browser = getBrowserType();
// now take some action
if (browser==='firefox') {
// do something in the Firefox way
...
} else if (browser==='chrome') {
// do something in the Google way
...
} else if (browser==='opera') {
// do something in the Opera way
...
} else if (browser==='ie') {
// do something in a crappy way
...
}
...
}
The hypothetical getBrowserType function is a example function that (maybe) perform some checks to know what browser is the one used by the visitor (who of you know the window.navigator.appCodeName can suggest that this getBrowserType function call simple this primitive code, however the "browser sniffing" is always best choice).

What's wrong with this approach? Every time we call crossBrowserMagicFunction we are repeating from scratch all the tests from knowing what kind of browser we are using.

To simplify this and perform less call to the same code we can obviously move all the code that check the browser type out of the function. You can suggest to perform this check only one time, when the page has been loaded, then save the result into a global variable (I'll use jQuery syntax for this example).
$(document).ready(function() {
// some lines of code to know what browser we have
...
BROWSER_TYPE = ...;
})
...
var crossBrowserMagicFunction = function() {
var browser = BROWSER_TYPE;
// now take some action
if (browser==='firefox') {
// do something in the Firefox way
...
} else if (browser==='chrome') {
// do something in the Google way
...
} else if (browser==='opera') {
// do something in the Opera way
...
} else if (browser==='ie') {
// do something in a crappy way
...
}
...
}
This can seems a final solution, but in last years the approach to perform "something" knowing exactly what kind of browser the visitor is using is becoming quickly obsolete.
A lot of JavaScript guides suggest you to perform instead a "browser features sniffing" operation.

Why?
Because you don't want to know what browser the visitor is using (who cares?). You want to know if (for example) the browser is able to play a music resource using HTML 5 support!
Even jQuery has deprecated old jQuery.browser features, suggesting to use instead jQuery.support. This approach also make your code compatible with future versions of a browser (a check that today works only with Firefox maybe tomorrow will work in IE 11).

What change this in our example?
Well: this invalidate the use of a global BROWSER_TYPE information. Our code can be full of functions crossBrowserMagicFuction functions, like:
  • playSound
  • playVideo
  • dragDrop
  • ...
... checks for browser features sniffing can (will) sniff for different features in different functions.

So we go back to the original code: inside the function we will perform our checks.
Now we must find a way to limit the problem of efficiency left: we need a way to perform the browser features checks only a time (at least, one time per function).

This is the way:
var crossBrowserFunction = function() {
function setup() {
... // let know what this browser is able to do
}
function doSomething () {
... // do really something info taken from setup
}
setup();
return doSomething;
}();
Done!

Just explain this a little: crossBrowserFunction is a self-invoking function, that create a JavaScript closure inside. A self-invoking function is defined and immediately called.

The two internal functions are private, but the second function doSomething are returned from the calling, so became public (note that we return doSomething, not the call of doSomething... we don't have any "()" at that line). So the result of defining crossBrowserFunction is obtaining a function that performed some check before.

In this way all the browser feature sniffing stuff is performed only one time.

Also important for me is the closure itself. Every variable defined inside the self-invoking function is available when we call later crossBrowserFunction, because in facts this is a reference to a function defined inside the closure!

For this reason I taken this title for the post.
This programming way remind me the behavior we have in a compiled language:
  • The "compile time" there is the defining an call of the self-invoking function.
  • Every use of the function obtained will be faster (no runtime check of browser features) and optimized for the current browser (platform).

2 comments:

  1. To be fare, it's very similar to Python's closure :)

    Can you give an example of "browser features sniffing"?

    Good post, very interesting.
    cheers
    Giacomo.

    ReplyDelete
  2. The best example is the one I put above: http://api.jquery.com/jQuery.support

    However you can think your own. For example: is ale the current browser to store HTML 5 persistent data?

    if (window.openDatabase) {
    ....
    }

    ReplyDelete