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

Monday, October 12, 2009

Named function expressions demystified III

Update For those interested about Internet Explorer scope resolution, I summarized everything in 5 slides.


This is hopefully the end of the Named function expressions demystified trilogy, where here you can find episode I, and episode II.
Juriy knows I am hard to convince, but apparently he is not better than me at all ...

Inglorious Correction

Sure, it's better than nothing, but after I have spent dunno how many tweets plus 2 posts, all I have obtained is a small correction in the whole article (and you have to scroll a bit before):

Generally, we can emulate function statements behavior from the previous example with this standards-compliant (and unfortunately, more verbose) code:

var foo;
if (true) {
foo = function foo(){ return 1; };
}
else {
foo = function foo() { return 2; };
};

// call the function, easy?
foo();


Above snippet is the best solution in the entire article but probably to avoid my name in article credits, and it does not matter since I have already said it's not about the copyright, and surely to avoid personal ego conflicts, the suggested one is a surrogate of above snippet, quite embarrassing from a developer point of view, isn't it?

var f; // create an alias, WHY!!!!!!!!!
if (true) {
f = function foo(){ return 1; };
}
else {
f = function foo() { return 2; };
};

// create a reference in order to remove a reference, WHY!!!!!!!!!!!
var foo = null;

// call the function via another alias, WHY!!!!!!!!!!!
f();

Above snippet is just a surrogate because the first one simply create an alias which will refer the proper function. Let's be simply developers avoiding obtuseness, OK?

Re Solution Pros

  1. it's standard, no excuses at all!
  2. variables on top, no way we can forget to nullify the function and we don't need to create a reference which aim is to remove a reference, cause the reference is already assigned, no memory problems at all (possibly less, since there is nothing referenced to a null value)
  3. semantic, we can easily refer to the function, since the whole point is to solve the missed arguments.callee plus IE inconsistency ...
  4. standard again, because it perfectly emulates ECMAScript 3rd Edition behavior even in Internet Explorer
  5. the day we will need to nullify the function will be the day we meant it, and not a surpassed convention


Why My Re Solution Is More Standard

This is the expected behavior in all browsers, except Internet Exlorer and Opera, via Internet Explorer emulation:

var f; // let's use the suggested alias
// for demonstration purpose
if (true) {
f = function foo(){
// this is LOGICAL
// but in IE it will be false
// because foo will be the
// the other one, even if that
// else will never be executed!
alert(foo == arguments.callee)
};
}
else {
// IE will declare this function
// in any case since there is NO DIFFERENCE
// between expression and declaration
f = function foo() { return 2; };
};

f();

With my Re Solution the behavior is the expected one, alert(foo == arguments.callee) will be true in every browser ... do you still have doubts?

Re Solution Cons

  1. unfortunately, more verbose
This must be a joke ... more verbose? First of all we are dealing with developers that don't care at all about verbosity. Kangax as everybody else in credits always preferred verbosity since minifier and compressor could take care about this verbosity recreating, and I love the irony of this part, exactly the suggested case.

Re Solution Pros II

  1. being the function name declared on the top of the function, as I have said a well known good practice, every IDE will automatically suggest that name as soon as we'll startt to type it: does verbosity matter?
  2. As we all know gzip and deflate compresses repeated words more efficiently, as result the Re Solution is even smaller if we don't munge it, does verbosity matter?


Re Solution Is More Logical And Smaller

Here the simple test everybody can do. Two pages, same code, except the first one is Re Solution, 99 bytes, while the second one is Juriy suggestion, 111 bytes.

<?php ob_start('ob_gzhandler'); ?>
var foo;
if (true) {
foo = function foo(){ return 1; };
}
else {
foo = function foo() { return 2; };
};


<?php ob_start('ob_gzhandler'); ?>
var f;
if (true) {
f = function foo(){ return 1; };
}
else {
f = function foo() { return 2; };
};
var foo = null;

Do we have a single valid reason to use the Juriy suggestion over mine? I would honestly feel an idiot preferring the second one, since few bytes and more logic (foo is the function foo) against spread variables declarations (top and the middle with F = null) via aliases rather than function names inside the function itself where a debugger will show the name but we have to remember the alias, plus the possibility we forget to nullify the reference consuming more memory ... I mean, this is not the first of April, isn't it?

And That's Not All Folks

We are developers, not monkey, I always dislike generic affirmations a la eval is evil, the most used function since Ajax epoch, obviously included in json2.js itself, since is natural and logical to use it when necessary. Juriy corrected his article to underline how unprofessional am I suggesting a last option for IE behavior ... well, probably he has never thought about ternary assignment, isn't it?
// Never do this!, that's what I can read at some point, but we should think carefully before these statements.
Here there is an example where the function will be a named one but there are no differences in this case for IE, indeed the last option is the IE one.

// somewhere in a closure ...
// (otherwise add will be public in IE,
// but I hope we went further than this
// at this point and after 3 posts
// plus an entire article ...)
var event = {
add:document.addEventListener?
function add(){
alert([add, document.addEventListener]);
}:
function add(){
alert([add, document.attachEvent]);
}
,
del:document.removeEventListener?
function del(){
alert([del, document.removeEventListener]);
}:
function del(){
alert([del, document.detachEvent]);
}
};

In one shot we have created a unique add reference, a named function, plus the right one for the event object. Now guess what's up if we invert the order putting the IE version at the end ... add will be the last option ... the one with addEventListener, got the point?

As Summary

I am pretty much sure I'll be criticized again and only for the last part of this post where obviously things work but somebody will argue about future IE9 sci-fi behavior or stuff like that ... well, that day my library will be deprecated in any case and, if needed, it's easy to implement over my Re Solution

var event = {
add:(function(add){document.addEventListener?
(add = function add(){
alert([add, document.addEventListener]);
}):
(add = function add(){
alert([add, document.attachEvent]);
}); return add
})()
};

Now let's see if the most interesting analysis about functions expressions and declaration will keep ending up with the wrong suggestion, rather than mine ... kinda curios, still hopeful though.

6 comments:

kangax said...

First of all, there seem to be a misunderstanding here.

Let me clear things up a bit.

NFE latest addition — function statements chapter — was not meant to be an answer to your posts. It's just something I've been meaning to write about for a long time (as you can see from the readme on github). The snippet you see in that chapter (foo = function foo(){}) is merely an emulation of function statements. It has nothing to do with final solution (although yes, the idea is the same).

Now, regarding your solution.

I'm having difficulties understanding your writings, but after rereading all of these posts few times, it seems like all you're trying to say is that we can use same name for identifier on left hand side of assignment and that of function "name".

Yes, that's a valid point, and is also something that I wanted to write about for a long time (see TODO which had that entry for at least couple of months now). The reason I was postponing this "alternative solution" is because I wanted to perform few memory tests — just to confirm my theories about memory use being similar to `null` -based approach.

As far as you continuing endorsement of function declarations in blocks — well, I've expressed my opinion many times before and have nothing else to add here. If you don't understand problems with this approach, there's not much I can do.

Enjoy using them further ;)

Cheers.

Andrea Giammarchi said...

Juriy, about testing, you must have misunderstood your same writings then. A reference name is a reference name and it comes after, it's another layer ... what you write for f is the same writing F, there is nothing different at all.
The same f is not overwritten in the other assignment is due to unconditioned top-down function IE parsing to demonstrate that there is no such difference for IE as I have already said dunno how many times.

In IE the difference DOES NOT EXIST, and the named reference, the variable, not the function, cannot have problems and in the same scope, that reference will be the only one for that function, so garbage removes it THE SAME WAY you do with var F = null, which comes after.

You seems to have difficulty to get my points but I provided any kind of test to demonstrate them.

Please don't be blind and about Re Solution, yes, I thought that little space, the most valid code, came after my post. I code in this way since ages, and last option is one of my favorite trick which is, as innerHTML, a de facto behavior.

We cannot talk about standards when we try to find solutions for a non standard behavior, the Internet Explorer one.

If we had standards to follow, your whole article would have been quite obvious and meaningless, from solution point of view, and all bugs section wouldn't be there.

Do you agree? You must be flexible about standards the second you have decided to support a closed source browser without bug repots possibilities as IE is.

To support Safari 2 you provided a function blocks example, it does not matter you assign the variable. Same is for IE ... so, whatever solution you'll write there, it will use function blocks/declarations in IE and it must use a Safari 2 convfention, so it won't be standard, and at least make it a solution, without "ridiculous" anti patterns as a required var functionName = null in the middle of the code is, and an alias that won't help debugging is as well.

This ... ASAP, imho, for the rest of the TODO take all time you need. It's just you know people often scroll to the solution, read zero, and learn 1 ... don't you?

Thanks fr the reply, and sorry for misunderstanding.

Regards

Andrea Giammarchi said...

P.S. check the ternary as well with IE as last option. The reference is the correct one inside the function in Opera as well, in IE as well, in Safari 2 I guess definitively as well.

You wrote nothing about ternary assignment, and behavior is the expected one with every browser, Opera included, except IE: and here there is the last option trick, a standard behavior, written to support a dedicated IE bug ... where last option is just the second one, being ternary, nothing complicated at all.

I honestly can't call this evil or "Never do this", but this is just my opinion.

kangax said...

My response doesn't fit in here, so here it is in a gist — https://gist.github.com/5cb2804d5ac732eabd78

Andrea Giammarchi said...

I basically stopped here:
Which layer? There are no layers in Javascript.
scope resolution layer

I'll red the rest later

Andrea Giammarchi said...

About standards ... my point is that you can explain standards, but you fail the behavior/description the second you say that the solution uses expressions rather than declaration.

JScript has never followed ECMAScript 262 and for JScript the behavior I described in scope resolution is standard, and not a bug.

Anti pattern is to position "randomly" an ambiguous variable assignment to null, dangerous in IE due privileged assignment, where the variable has even the name of the function we would like to use, and avoiding good and logical practices: variables on top and expected name reference for the expected named function.

The "Never do this" is quite explicit and does not sounds like a suggestion.

I hope my last 5 little slides will be enlightening and I hope you'll change the suggestion ASAP 'cause you have no points to keep it there.

If it's a matter of time, add the proper suggestion with a little comment (describing soon), no?

Thanks for the reply.