Tuesday 2 December 2008 — This is almost 16 years old. Be careful.
Digging into a mysterious failure today at work, I discovered an odd factoid about the way Internet Explorer interprets Javascript.
Here’s a small sample HTML file with a chunk of Javascript:
<html>
<head>
<script>
/* When are functions defined? */
function really() { alert("Original"); }
if (0) {
alert("No");
function really() { alert("Yes, really"); }
}
really();
</script>
</head>
<body>Really</body>
</html>
What alerts will display when you load this page? In Firefox, you’ll see “Original”. In Internet Explorer 6 (and I think 7), you’ll see “Yes, really”. In neither browser will you see “No”.
In IE, the redefinition of really() is interpreted and used even though it is inside a block of code that is not executed. Wonder of wonders...
You can fix it by changing the redefinition clause to:
if (0) {
alert("No");
var really = function() { alert("Yes, really!"); }
}
Oh, Internet Explorer, how can you be right when you feel so wrong?
Comments
I guess this has to do with the way IE interprets it. I once read that it first passes thru the whole code looking for function definition and attaches it to window object. This follow the actual execution of the code. Your fix doesn't update the function definition during the first pass.
I believe this does justify the behavior.
var foo = function() {} is the assignment of an anonymous function to the name "foo", whereas function foo() {} defines the function foo in the enclosing scope.
(For additional amusement var foo = function bar() { } will only give the variable foo a value. bar will not be set, and the only use is so you can reference a function for recursion.)
For a perhaps better understanding of why this is so, the first is a function definition as a statement, and the second is a function expression.
Considering Richard's comment, this seems to be more of a bug in Firefox, doesn't it?
I also *really* wish that I had access to a repository somewhere that documented the previous 1375 IE mysteries. Anyhow, thanks for explaining a very peculiar behavior.
I just skimmed ECMA-262 and the (short) section on scope appears to reinforce the idea that this may be a bug on Firefox's side.
What's next? Are we going to hear that function definitions inside commented-out lines are still "supposed" to execute?
Javascript is actually braindead simple, in that to find out what a variable/function name is bound to, you can just look up the actual source code, and the prior occurrence of that variable in the function scope you are currently in is the source.
I always use object.member = function () {} or var member = function () {} style to avoid this issue, and I've only ever seen one prominent JavaScript developer take advantage of the forward-declaration pattern (Oliver Steele, I think), and I've ported or written about 100KLOC of JavaScript while building Chiron.
Spec covers this case, if multiple function declarations (in the same execution context) have the same name the last one should be used.
Firefox clearly handles it wrong, "if" statement doesn't originate new execution context.
Brendan invented Javascript, I guess he knows how it is supposed to work.
Not intuitive at all:
alert(evaluate('(function() { function a() { return 1 }; if (1) function a() { return 2 }; return a(); })();')); // 2
But Firefox do not.
This isn't really about the semantics of scoping, this is about the semantics of code execution, and what (if anything) it means for a function definition to have executed vs not executed.
It seems that in IE, a function definition is considered something declarative which results in the definition being made at parse-time regardless of whether the declaration is executed or not. In firefox, the declaration only takes effect at runtime if it is executed.
I think the latter is definitely more what I'd expect in a dynamic language - does the spec have anything precise to say about this?
There's also the question of how IE decides which scope to place the function definition in, if it has to make that call at parse time rather than runtime. Perhaps this is always possible to do lexically but I have a feeling there might be some more gotchas.
To my mind, if a function statement is intended to have declarative semantics and not be a unit of code execution, then it has no place being allowed inside procedural control flow constructs. It should be limited to the top-level only.
There's no bug here in any browser. The code is invalid ECMAScript, but ECMAScript implementations are not required to throw a syntax error in this case. Instead, they can extend the syntax - and different browsers handle this particular code differently, which the standard allows them to do.
The original code is invalid ECMAScript because it has a FunctionDeclaration inside a Block. In ECMAScript, a FunctionDeclaration may only appear in a Program or in a FunctionBody. In other words, it must be either directly in the global scope or directly inside another function - not in any kind of block statement. (See sections A.4 and A.5 of the ECMA-262 standard for the syntax details.)
A FunctionExpression is not subject to this limitation. It may appear anywhere that any other expression may appear. That's why the revised example works consistently in all browsers. It's valid ECMAScript code, with a FunctionExpression instead of a FunctionDeclaration. Unlike a FunctionDeclaration, a FunctionExpression is evaluated at runtime like any other expression. Being inside an if(0){}, this FunctionExpression is not evaluated at all.
The JScript interpreter in IE extends the ECMAScript syntax to allow a FunctionDeclaration to appear inside a Block, and it's treated just like any other FunctionDeclaration. These are processed before the code is executed, and if there's more than one with the same name, the last one wins. This is true even if the FunctionDeclaration is inside a block that's never executed - because the declaration has already been processed.
The JavaScript interpreters in Firefox and many other browsers have a different syntax extension. They consider this code to be a FunctionStatement, which is executed at runtime just like a FunctionExpression. So, in these browsers the first instance of the really() function is a FunctionDeclaration which is processed before any of the code executes, and the second instance of the really() function is a FunctionStatement - which is not executed at all because it's inside the if(0){}.
Add a comment: