Monday, December 29, 2008

Function declaration vs. function assignment in JavaScript

I'm surprised how much I learn about JavaScript every single day.

It all started with a post that's wrongly titled to indicate a bug in Internet Explorer. The comments are an interesting read, where people go ahead to suggest that this "bug" exists in all browsers except Firefox. Then, they discover that it's actually a bug in Firefox, and not the other way round. Then a bug report is filed with Firefox. Brendan Eich comes along and resolves it as an invalid bug, with a brief explanation.

To explain, here's what's happening:

Ned Batchelder (the poster of that blog post) came across this piece of code that worked differently in IE and Firefox.

function really() { alert("Original"); }
if (0) {
function really() { alert("Yes, really"); }

The explanation is pretty simple. A function really is defined which alerts "Original". Inside an if block which should never get called, the really function is redefined to alert "Yes, really". Since the code block is inside an if that should never get called, the assumption would be that the function shouldn't get redefined. This is how Firefox behaves. However, not so with IE, which redefines the function. Weirdly, the alert("No"); is never executed, even though the function gets redefined.

Turns out, IE is not the only one doing this. All webkit browsers and Opera also exhibit this behavior. Kris Kowal was the first to suggest that this is a bug in Firefox for the right reasons. So, a bug report was created and BE closed it. Here's his explanation:

ECMA-262 Edition does not specify functions in sub-statements, only at top level in another function or a program (see the normative grammar). Therefore this bug is invalid as summarized.

Moreover, we have extended ES3 (as allowed by its chapter 16) for ~ten years to support "function statements" whose bindings depend on control flow. So this again is invalid. There's a bug to dup against, if someone finds it feel free to change resolution accordingly.


For those who didn't understand, here's a simpler version of what BE said.

  • function really() { ... } is a function declaration.
  • Function declarations can only exist at the top level, or inside other functions. They cannot be inside the if statement.
  • If the browser finds that it is inside the if block, the browser is not allowed to throw an error. Instead, it should handle it gracefully.
  • There's no spec that defines how such situations should be handled, so browsers are free to do what they want to.
  • All browsers except Firefox first scan the code at load time to find function declarations. When doing so, they look at the function first, and then it's redefinition. In this case, the original function should be discarded, and the new function should be used.
  • Firefox does this at runtime, evaluating the functions when the code execution sees it. Hence Firefox doesn't redefine the function at all.

So, BE's explanation that this is not a bug is valid. This is a gray area in the spec, and the browser is free to implement it the way it wants.

Also, and more interestingly, BE brings up function statements which do not follow this rule. A function statement would look like this:

var really = function() { ... };

Now, the spec very clearly says that function statements should be evaluated at runtime based on control flow - stuff like that if statement. Ned wouldn't have run into this problem if he was using function statements.

I learn new things about JavaScript every day!