I asked this question a while back and was happy with the accepted answer. I just now realized, however, that the following technique:
var testaroo = 0;
(function executeOnLoad() {
if (testaroo++ < 5) {
setTimeout(executeOnLoad, 25);
return;
}
alert(testaroo); // alerts "6"
})();
returns the result I expect. If T.J.Crowder's answer from my first question is correct, then shouldn't this technique not work?
Well, it will work, the problem with JScript (IE), is that the identifier of the function expression (
executeOnLoad
) will leak to its enclosing scope, and actually creating two function objects..A very good question. :-)
The difference:
The difference between this and your
detachEvent
situation is that here, you don't care that the function reference inside and outside "the function" is the same, just that the code be the same. In thedetachEvent
situation, it mattered that you see the same function reference inside and outside "the function" because that's howdetachEvent
works, by detaching the specific function you give it.Two functions?
Yes. CMS pointed out that IE (JScript) creates two functions when it sees a named function expression like the one in your code. (We'll come back to this.) The interesting thing is that you're calling both of them. Yes, really. :-) The initial call calls the function returned by the expression, and then all of the calls using the name call the the other one.
Modifying your code slightly can make this a bit clearer:
The
f();
at the end calls the function that was returned by the function expression, but interestingly, that function is only called once. All the other times, when it's called via theexecuteOnLoad
reference, it's the other function that gets called. But since the two functions both close over the same data (which includes thetestaroo
variable) and they have the same code, the effect is very like there being just one function. We can demonstrate there are two, though, much the way CMS did:This isn't just some artifact of the names, either, there really are two functions being created by JScript.
What's going on?
Basically, the people implementing JScript apparently decided to process named function expressions both as function declarations and as function expressions, creating two function objects in the process (one from the "declaration," one from the "expression") and almost certainly doing so at different times. This is completely wrong, but it's what they did. Surprisingly, even the new JScript in IE8 continues this behavior.
That's why your code sees (and uses) two different functions. It's also the reason for the name "leak" that CMS mentioned. Shifting to a slightly modified copy of his example:
As he mentioned,
inner
is defined on IE (JScript) but not on other browsers. Why not? To the casual observer, aside from the two functions thing, JScript's behavior with regard to the function name seems correct. After all, only functions introduce new scope in Javascript, right? And theinner
function is clearly defined inouter
. But the spec actually went to pains to say no, that symbol is not defined inouter
(even going so far as to delve into details about how implementations avoid it without breaking other rules). It's covered in Section 13 (in both the 3rd and 5th edition specs). Here's the relevant high-level quote:Why did they go to this trouble? I don't know, but I suspect it relates to the fact that function declarations are evaluated before any statement code (step-by-step code) is executed, whereas function expressions — like all expressions — are evaluated as part of the statement code, when they're reached in the control flow. Consider:
When the control flow enters function
foo
, one of the first things that happens is that thebar
function is instantiated and bound to the symbolbar
; only then does the interpreter start processing the statements infoo
's function body. That's why the call tobar
at the top works.But here:
The function expression is evaluated when it's reached (well, probably; we can't be sure some implementations don't do it earlier). One good reason the expression isn't (or shouldn't be) evaluated earlier is:
...doing it earlier leads to ambiguity and/or wasted effort. Which leads to the question of what should happen here:
Which
bar
gets called at the beginning? The way the specification authors chose to address that situation was to say thatbar
is not defined infoo
at all, hence side-stepping the issue entirely. (It's not the only way they could have addressed it, but it seems to be the way they chose to do so.)So how does IE (JScript) process that? The
bar
called at the beginning alerts "Hi (2)!". This, combined with the fact we know two function objects are created based on our other tests, is the clearest indication that JScript processes named function expressions as function declarations and function expressions, because that's exactly what is supposed to happen here:There we have two function declarations with the same name. Syntax error? You'd think so, but it isn't. The specification clearly allows it, and says that the second declaration in source code order "wins." From Section 10.1.3 of the 3rd edition spec:
(The "variable object" is how symbols get resolved; that's a whole 'nother topic.) It's just as unambiguous in the 5th edition (Section 10.5), but, um, a lot less quotable.
So it's just IE, then?
Just to be clear, IE isn't the only browser that has (or had) unusual handling of NFEs, although they're getting pretty lonely (a pretty big Safari issue has been fixed, for instance). It's just that JScript has a really big quirk in this regard. But come to that, I think it actually is the only current major implementation with any really big issue — be interested to know of any others, if anyone knows of them.
Where we stand
Given all of the above, for the moment, I stay away from NFEs because I (like most people) have to support JScript. After all, it's easy enough to use a function declaration and then refer to it later (or indeed, earlier) with a variable:
...and that works reliably across browsers, avoiding issues like your
detachEvent
problem. Other reasonable people solve the problem differently, just accepting that two functions will get created and trying to minimize the impact, but I don't like that answer at all because of exactly what happened to you withdetachEvent
.