Internet explorer mystery #1376

Tuesday 2 December 2008This is 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

[gravatar]
nice find. IE is still learning to hide its nemesis.
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
 var really = function() { alert("Yes, really!"); }
doesn't update the function definition during the first pass.

I believe this does justify the behavior.
[gravatar]
Thanks for posting this, that's pretty interesting/annoying.
[gravatar]
Reminds me of the horrible hacks people put in CSS files designed to be interpreted wrongly by IE and skipped by conformant parsers. You could use this as a different way to write IE-specific versions of functions, if you had temporarily forgotten how to use the navigator object to sniff which browser your code was running in … :–)
[gravatar]
Actually function foo() { } and var foo = function() { } are different in Javascript.

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.
[gravatar]
AFAIK, the described behaviour is a JavaScript feature called "hoisting".
[gravatar]
Chrome and Safari have the same behavior as IE in this example.
[gravatar]
The behaviour actually makes sense, as there is no block-scope in JavaScript, and function definitions are parsed before the surrounding code is executed.

Considering Richard's comment, this seems to be more of a bug in Firefox, doesn't it?
[gravatar]
Technically, p3k and Jörn are right, but I see this as a bug in the LANGUAGE JavaScript, and by gum shouldn't we fix it! Because the hoisting behavior is completely absurd.

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.
[gravatar]
I agree with Jörn on this. On my first read-through as well, I was like "that looks right". As JS is lexically scoped, and there are only two scopes (global and function), it does appear that IE, Chrome, and Safari are doing it right.

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.
[gravatar]
Well, what do you know? Turns out Safari, Chrome, and Opera all agree with IE on this one, so it looks like a Firefox bug! Will the wonders never cease?
[gravatar]
OK, call me stupid but I don't get it; how is this meant to be the "right" behavior? Scoping considerations aside, the definition occurs inside a block *which should never execute*. Wrapping something in "if(0){}" or "if(false){}" or equivalent should prevent the wrapped code from ever executing or carrying out any operation in any way.

What's next? Are we going to hear that function definitions inside commented-out lines are still "supposed" to execute?
[gravatar]
I think Michael stated it best that it seems to be a bug with the language itself. I know lexical scoping bugs have bitten me before, as intuitively, things don't work like you think they *should* but by the ECMA-262 spec, they are technically correct. I would occasionally run into problems like this one guy had: http://ayende.com/Blog/archive/2007/12/13/Javascript-lexical-scopes-and-what-your-momma-thought-you-about.aspx before I got my ahead around what JS was doing.

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.
[gravatar]
This is a proper behavior of JavaScript that permits forward-references to functions, a distinctly static-language feature. I wouldn't miss it, but it makes this code possible:
main();
function main () {
    log("Hello, World!");
}
So, I wouldn't be hasty to write this off as a spec-bug. The distinction between Firefox and other browsers might not even be a spec-conformance-bug depending on whether the spec addresses the behavior when multiple function declarations have the same name.

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.
[gravatar]
@Kris Kowal

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.
[gravatar]
I tried Brendan Eich's Javascript interpreter written in Javascript (http://mxr.mozilla.org/mozilla/source/js/narcissus/) and it works just like Firefox.
Brendan invented Javascript, I guess he knows how it is supposed to work.
Not intuitive at all:
(function() {
  function a() { return 1 };
  if (1) function a() { return 2 };
  return a();
})(); // 1
[gravatar]
Sorry, Brendan's code works right:
alert(evaluate('(function() { function a() { return 1 }; if (1) function a() { return 2 }; return a(); })();')); // 2

But Firefox do not.
[gravatar]
Guys,

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.
[gravatar]
Interesting that Brendan Eich closed the bug report to Mozilla as invalid.
[gravatar]
Brendan Eich's response to DivineGod's bug report partly explains what's going on. Here is the rest of the story.

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){}.
[gravatar]
thanks for fix.. had need it. thanks again.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.