My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Tuesday, March 16, 2010

Anonymous Style

When a function is to be invoked immediately, the entire invocation expression should be wrapped in parens so that it is clear that the value being produced is the result of the function and not the function itself.

I have already commented the popular JavaScript Code Convention article, but latest cited sentence is probably the only one I have never been sure about.

Why Bother

Actually, I have always found the expression:

var something = function(){

// do stuff
// return something

}(); // invoke
kinda enough to invoke inline a function expression.
Somebody argued that above style is ambiguous.
Since an inline invoke could be performed against a massive function body, the point is that we may need to scroll 'till the end of the function expression to know if it has been executed or not.
In my opinion, whenever there is a function expression, we should always check the end of this function or we'll never be sure about the assigned value.
Fair enough, even if the function could not be necessary invoked:

var something = function(){

// do stuff
// return or not

}.bind(this);
expressions are usually wrapped in any case regardless what's up at the end.

Still ... Why Bother

The most common implementation of this code convention we can find almost everywhere, included my shared code, is this one:

var something = (function(){

// do stuff
// return something

})(); // invoke

What is actually wrong with this implementation?
Let's simply imagine the function returns a function:

var something = (function(){
return function(){
return 123;
};
})();

Now, let's imagine that we would like to execute runtime the returned function, since we know that expression will return one:

var something = (function(){
return function(){
return 123;
};
})()(); // <= WTF?

I am pretty sure that ()() at the end of a runtime invoked expression, will bring us back to origins, where "we had to check 'till the end of the function", at least to understand what is going on ...
In few words, and as I have said, the scroll 'till the end of whatever function should be mandatory, if the code is not our one, and we would like to understand what this code actually does.

Non Ambiguously Speaking

If we follow Mr D. suggestion, we could say that this strategy will be rarely ambiguous.
To spot a wrap is definitively more easy than spot a potential typo, as 4 brackets could suggest:

var something = (function(){
return function(){
return 123;
};
}());
With latter snippet we can certainly say with a quick look that something refers the value returned by the invoked expression. It's wrapped, it must be an invoke rather than a function assignment, and it could be whatever value.
The instant we add other brackets, we are obviously dealing with the returned value:

var something = (function(){
return function(){
return 123;
};
}())(); // invoke the returned value

In other words we are able to split the inline invoked expression, with the rest of the logic, but still scrolling through the end!


Moreover

The usage of brackets makes the assignment superfluous:

// first case analyzed without brackets
(function(){

// do stuff

}());
In this case we are clearly creating a closure to avoid global space pollution and to perform some useful operation.
Since this is basically what we can find almost everywhere in many different libraries or piece of libraries, and since we have some shortcut to instantly reach the end of a file, why should we put the inline invoke outside the operation?

// first case analyzed without brackets
(function(){

// do stuff

})();
The sequence of )() could suggest that something happened before and somehow it constrict us to check if there are other 2 round brackets before those spotted at the end.
Furthermore, if we have an expression this is what we could write:

var something;

// other code

(something = function(){

// do stuff

})();
Again, unless we did mean that operation, maybe to avoid IE problems with named function expressions, those brackets outside the "wrap" could truly be considered ambiguous: was I trying to assign the expression result or the function itself?

something = (function(){

// do stuff
// return stuff

}());
There we have again a clear statement of what we were planning to do, isn't it?

Anonymous Assignment

We could think about some common pattern we can consider less error prone, and less ambiguous ... the first one is the classic assignment, performed for prototype properties, to avoid IE problems, or simply assign callbacks:

something = function(){};


Inline Invoke

We may agree that next one is the bets way to make an inline invoke non ambiguous:

something = (function(){}());

Same is if we are not returning anything, so that the expression will be called simply because we need a closure:

(function(){}());


Inline Constructor

This is not truly such common technique, but there is a massive difference if we use brackets or not.

var a = new (function(){
return Array;
}());
// true
alert(a instanceof Array);


var a = new function(){
return Array;
}();
// false
alert(a instanceof Array);
// a is actually the Array constructor
alert(a === Array);

The usage of new before an expression is something more suitable for ternary operator:

var o = new (typeof null !== "object" ? Object : String);

not particularly suitable for inline constructor concept.
This simply means, in my opinion, that if we meant to use the function as a Singleton, we should never put brackets around:

var Singleton = new function () {
var secret = "Singleton Baby!";
this.constructor = Object;
this.toString = function () {
return secret;
};
};

alert(Singleton);
// Singleton Baby!


As Summary

Earlier, I found the initial advice kinda redundant or superfluous, but I must admit it could make sense specially in weird cases, and this is why I have personally started to adopt it without feeling less readable at all.
Is there any other case where we could try to avoid expression ambiguity?

No comments: