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

Saturday, October 10, 2009

Named function expressions demystified

Update If after this reading things are still the same, please read the part 2 of this post, thanks.



This is a re-post, and few considerations, about the good Juriy article, which I suggest for every JavaScript developer with a deeper knowledge than just an API (jQuery or others).

Github For Everything!

My first consideration is about github, something I've never used that much since via Google Code I feel pretty comfortable with subversion. I find truly interesting the way Juriy is tracking his documentation, I've never thought about an article, as my old JavaScript Prototypal Inheritance could be, in a code repository as kangax did: good stuff!

My Alternative Solution

There are few extra consideration to do over Juriy explanation, plus minor inconsistencies. The first thing is that Internet Explorer basically manages Function expressions and Function declarations in the same way, there's no such VS in the middle.
The fact we assign the function to a whatever named variable does not make any difference:

alert(F); // (function F(f){/*IE Function*/return f})

F((function F(f){/*Function Declaration*/return f}));
F((function F(f){/*IE Function*/return f}));

var f = (function F(){return F}); // <== which F?
function F(){/*IE Function*/}; // this one!

alert(f()); // function F(){/*IE Function*/};
We can play with above IE traps for ages but the point is simple: the last defined function with that name, will simply take the place of the other one, if any, in the same scope, and at the end of its resolution, before code execution.

What IE does is basically a top-down parsing over functions without taking care at all about code execution or normal and expected syntax execution flow.
This simply means that return whatever, var whatever = function whatever(){} ... does not change anything for our loved IE:what we see or what we expect is not what will be, otherwise IE would have been a perfect browser.
To better understand what I am talking about, this example should speak for me:

var f = 123;
alert(F); // last function
(function(){
alert(F); // (function F(){alert(f);return arguments.callee})
var f = (function F(){alert(f);return arguments.callee})();
// undefined
})();
function F(){};

After these two snippets is quite logical understand this behavior:
  1. scope resultion
  2. function resolution plus optional inline assignment, if any, before next function resolution
  3. code interpretation/execution over scope resolution

Accordingly with this Richard Cornford post, in JScript every function is performed sending the execution context, a behavior somehow similar to the injected context via eval in Firefox few months ago.
The latter one has been considered a security hole ... same kind of hole we need to deal with IE on daily basis every time we would simply chose a name for a bloody function.

My Solution

Juriy perfectly knows my point of view about this problem, he knows it so well that his addEvent solution example is created ad hoc to make mine inefficient (and I'll tell you later why).
It does not matter, as long as I can use the same example, avoiding IE4 support ...

var addEvent = (function(){
var docEl = document.documentElement;
if (docEl.addEventListener) {
/* return */ function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
};
} else {
// IE has to be the last option
// other addEvent will be "lost"
// cause this will be the only named reference
// in this scope ...
function addEvent(element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
}
};
return addEvent;
})();

The key is simple, and is based exactly over same considerations and suggestions Juriy gives in the Alternative solution paragraph, except my suggestion uses an explicit last option callback, without requiring var this or that plus var this or that = null in any place, and for this purpose.

Indeed, for the same reason Juriy did not perform this task at the end of his latest suggestion and before the returned function:

if (typeof docEl.addEventListener != 'undefined') {
attachEvent = addEventAsProperty = null;
return addEventListener;
}
elseif (typeof docEl.attachEvent != 'undefined') {
addEventListener = addEventAsProperty = null;
return attachEvent;
}
addEventListener = attachEvent = null;
return addEventAsProperty;


totally boring and absolutely anti pattern, on daily basis and real case scenarios, with my suggestion there is absolutely no need to remember to nullify variables used only to refer inline assignments and, moreover, only as Internet Explorer workaround!!! That's too much, I mean we have to change our code, change JS logic, to support such drama JS engine, we even need to write more and nullify everything? No way!

But It Could Be Inefficient

The only real side effect about my suggestion, surprisingly working in Safari 2 as well and solving its problems with names, is that if we have to deal with two different versions of IE in the same scope, we cannot use the "last definition" trick, 'cause one out of 2, 3, or 234567 versions of the most problematic browser since Web epoc, still Internet Explorer and JavaScript speaking, will mess up like a charm ... to be honest, IE4 days, the one without try catch and much more support, are far away from 2009, and I've never had to deal with such problem but in this case, there is nothing better, so far, than Juriy proposal.

Update
In my addEvent example Opera, thanks to its duality, will behave IE like. This is not a problem, since addEvent will work in any case, but we can return if we would like to force Opera with addEventListener (avoiding Safari 2 then). To solve this problem, when necessarym we can use the best from both proposal.

Re Solution


var addEvent = (function(){
var docEl = document.documentElement,
addEvent;
if (docEl.addEventListener) {
addEvent = function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
}
}
else {
addEvent = function addEvent(element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
}
}
return addEvent;
})();
Quite semantic, isn't it? ;)
The principle is still the same suggested by Juriy except there is nothing to nullify, just a reference to return, and being based on variable assignment, we can have 2345IE versions in the if else without problems at all.

As Summary

In Internet Explorer there is no difference between function expression and function declaration, this is the whole point of this post, plus the updated suggestion which makes things a bit more logical, from a developer point of view - addEvent is a well defined reference, and that's what we need.
The var F = null; in Juriy suggestion is useless for IE. References comes after, in the interpretation flow, while references come before in the execution one. var F = null; will nullify a reference, it won't mark anything for the IE garbage collector.
Juriy article is in any case a must read as soon as we understand JavaScript scope and lambdas, and specially if we would like to support multiple browsers.
Hopefully these gotchas will disappear with ES5 and the next "dreamed" IE9 with its totally new fabulous V8 engine ( OK, OK, ... let me dream please ... )
The day IE will disappear from browsers panorama I'll be the most happy and drunk web developer in the area but until that day, we can say the panorama is still weird, at least well explained, and with all possible solutions, we have choices!

2 comments:

  1. Andrea, thanks for a review.

    On to the points:


    Github For Everything!My first consideration is about github, something I've never used that much since via Google Code I feel pretty comfortable with subversion.


    You really should version your article on github too (especially if it doesn't have/need comments).


    The first thing is that Internet Explorer basically manages Function expressions and Function declarations in the same way, there's no such VS in the middle.


    Not really, but close. JScript only parses *named* function expressions as function declarations (effectively ignoring that these expressions are in blocks (possibly "dead" blocks)). Anonymous function expressions are still expressions, and have nothing to do with function declarations.


    We can play with above IE traps for ages but the point is simple: the last defined function with that name, will simply take the place of the other one, if any, in the same scope, and at the end of its resolution, before code execution.


    No need to "play with them for ages". Function declarations in blocks should simply be avoided ;)


    What IE does is basically a top-down parsing over functions without taking care at all about code execution or normal and expected syntax execution flow.


    Yes, top-down is how function declarations are parsed per specs, so since JScript thinks named expressions are declarations, it follows same semantics and overwrites same-named function declarations as they are in the source.


    Accordingly with this Richard Cornford post, in JScript every function is performed sending the execution context, a behavior somehow similar to the injected context via eval in Firefox few months ago.


    I'm not sure what you're saying. How can execution context be "sent". Where is it being sent and how?


    The key is simple, and is based exactly over same considerations and suggestions Juriy gives in the Alternative solution paragraph, except my suggestion uses an explicit last option callback, without requiring var this or that plus var this or that = null in any place, and for this purpose.


    I don't understand why you would want to rely on unspecified behavior (function declarations in blocks). I mentioned many times how fragile it really is (http://groups.google.com/group/jquery-dev/msg/7dff428fa6aa98a2).

    As far as `null`ing goes, I'm not sure if your solution leads to a lesser memory consumption. Even if it does, I can't recommend using declarations in blocks. Sorry, it's just not worth it :)


    Update
    In my addEvent example Opera, thanks to its duality, will behave IE like.


    There we go... This proves my point about fragility, doesn't it? ;)


    addEvent = function addEvent(element, eventName, callback) {
    element.addEventListener(eventName, callback, false);


    Yep, I have it in my todo list (which, btw, you can see on github) to explain `var f = function f(){}` memory consumption in IE. Will update as soon as I have a chance.


    As Summary In Internet Explorer there is no difference between function expression and function declaration


    Of course, there is. It's just that named expressions create additional function objects after being parsed as func. declarations. Expressions are still expressions and declarations are still declarations. Moreover, as you could see explained in an article, expressions and declarations result in different function objects.


    var F = null; will nullify a reference, it won't mark anything for the IE garbage collector.


    I'm not following your logic. `null`ifying variable breaks reference to whatever object that variable was referencing. If that was the last reference (as it is in our case), GC is allowed to get rid of an object.

    Am I missing something?

    As always, I appreciate your critique ;)

    Cheers.

    ReplyDelete
  2. first of all there is another post with better explanation and hopefully a better suggestion for your article as well.
    The concept is the same but no need to split variables creations, all variables on the top, and no need to nullify anything since that will be implicit.

    I'll go throw your point but please read part 2 before, thanks.

    Anonymous function expressions are still expressions, and have nothing to do with function declarations.
    Juriy, IE threats everything in the same way.
    var f = (function(){});
    alert(f); // brackets included

    The fact a function has no name for IE simply means its reference is not explicit so unless you do not assign it inline, that function will be lost exactly as named functions. What you are talking about is JavaScript specs, something aline for IE and sometimes the silly Opera with its identity crisis.


    No need to "play with them for ages". Function declarations in blocks should simply be avoided

    Your solutions are all based, from IE point of view, in function declarations! So my last one is!

    Yes, top-down is how function declarations are parsed per specs
    Of course, but the top-down I am talking about is the IE one, where everything is parsed 'till the end.
    This is not in specs for other browsers, otherwise we would not have this problem.
    (function(){

    function F(){};
    F.itsAMe = true;

    if(false)
    function F(){};

    alert(F.itsAMe); // true
    // not in IE though

    })();

    Got the point about conditional and unconditioned top down parsing?

    I'm not sure what you're saying. How can execution context be "sent". Where is it being sent and how?
    During function execution. The surrounded context is defined on call time, that is why the same function produces 2 results inside a with, modified external context, and the global call.

    I don't understand why you would want to rely on unspecified behavior (function declarations in blocks)Because there are de facto standards and when there is IE in the middle "specs" are utopia and you know this better than me.

    Even if it does, I can't recommend using declarations in blocks again, for IE there is no such difference as you said at the beginning. The fact you are assigning does not change ANYTHING in IE and the whole point is to solve IE problems, isn't it?
    In any case Re Solution use assignment, and performances can be only better while memory consuption will be exactly the same cause f, or F, or $F, won't make any difference, except you have to redefine a reference via var F and nullify it.

    Hope you'll be more flexible than somebody else 'cause Re Solution has nothing wrong, until demonstration.

    Thanks in any case for your time and your reply.

    Cheers

    ReplyDelete

Note: Only a member of this blog may post a comment.