Why does IE nuke window.ABC variables?

When running the following block of code, FF and Chrome output typeof(hiya) = string while IE7/8 output typeof(hiya) = undefined.

<html>
    <body>
        <script type="text/javascript">
            window.hiya = 'hiya';
        </script>
        <script type="text/javascript">
            if( false ) {
                var hiya = 1;
            }
            document.write( "typeof(hiya) = "+ typeof(hiya) );
        </script>
    </body>
</html>

Each of the following makes the problem go away:

  • Combining everything into a single <script> block.
  • Removing the if block.
  • Renaming var hiya = 1 to var hiya2 = 1.
  • Renaming var hiya = 1 to window.hiya = 1.
  • Renaming var hiya = 1 to hiya = 1.

What is happening? Is there a scoping bug in IE?

Answers


IE is dumb, it doesn't recognize that window.varName and var varName access the same variable in some cases.

When a new script tag is encountered, it first initializes all the variables declared with var. It doesn't run the var statement (the part that would initialize it to "hiya"). It just initializes it to undefined. It won't do that if it was previously declared with var though.

If your code was in a single script tag, this error would not happen. Also, if the first declaration of hiya was done with var, this error also wouldn't happen.

Specifically, in your second script tag, IE first looks for var statements, it finds a var var hiya = 1; Then it says, hiya hasn't been initialized with a var statements previously (IE being dumb, other browsers recognize that window.hiya does the same thing) and initializes hiya, overwriting window.hiya before executing any code.

Possible solutions:

  • Keep your code within the same script tag
  • Do not initialize variables with window.hiYa
  • If you don't control over one of the scripts, make sure the script that uses var comes first

Last note to clarify what JS parsers do to your code. When the JS parser sees your code, it transforms it into the following:

<html>
    <body>
        <script type="text/javascript">
            window.hiya = 'hiya';
        </script>
        <script type="text/javascript">
            // IE is dumb, it doesn't recognize that hiya is already 
            // defined as window.hiya, so it's initialized to undefined here
            var hiya;
            if( false ) {
                hiya = 1;
            }
            document.write( "typeof(hiya) = "+ typeof(hiya) );
        </script>
    </body>
</html>

So if you put everything into one script tag, this is what the code would be (after the JS engine moved the var statements to the top), so you can see that there is no way the IE could mess it up, since your window.hiya assignment would be after the var that was moved to the top.

<html>
    <body>
        <script type="text/javascript">
            var hiya;
            window.hiya = 'hiya';
            if( false ) {
                hiya = 1;
            }
            document.write( "typeof(hiya) = "+ typeof(hiya) );
        </script>
    </body>
</html>

The core issue can be seen here http://jsfiddle.net/Raynos/UxrVQ/ I have yet to find out why IE overwrites window.hiya without checking.

[Edit]

From the specification. Page 38:

For each VariableDeclaration or VariableDeclarationNoIn in the code, create a property of the variable object whose name is the Identifier in the VariableDeclaration or VariableDeclarationNoIn, whose value is undefined and whose attributes are determined by the type of code. If there is already a property of the variable object with the name of a declared variable, the value of the property and its attributes are not changed.

A possible explanation could be that in global scope IE differentiates between the window object and the variable object for global scope when declaring variables. Alternatively setting a property on the window object directly might not set the same property on the variable object. If you can find a formal JScript specification or have the source of IE lying around then we can find out exactly what the quirk is.

[/Edit]

Thanks to @TimDown & @JuanMendes pointing out that's an issue with whether writing a property to the window object is a variable declaration.

The Issue:

variable declaration gets moved to the top of the block. Even if the code is dead. In IE for some reason it will declare hiya as a local variable even though it classes with the property of the same name stored on window.

Explanation:

What's happening is that your declaring a variable called hiya. The var statement automatically gets removed to the top of the block. An if statement isn't a block, a function is. So event if the code never gets run in the block the variable still gets declared.

In firefox it'll recognise that window.hiya is a declaration of hiya.

In IE the declaration in the second script overwrites it

What it's actaully doing

In firefox:

// script block 1
var hiya; // window.hiya counts as a declaration
window.hiya = "hiya"; // set

// script block 2
if (false) hiya = 1;
document.write(...)

In IE:

// script block 1
window.hiya = "hiya";

// script block 2
var hiya; // redeclared here because window.hiya "isn't" a declaration
if (false) hiya = 1; 
document.write(...)

The solution is simply namespacing. You're using the same name in two places and accessing it in two different names. Either use different names or use closures to give local scope.


What you encountered is due to:

  1. var being a statement
  2. There's no block scope in JS
  3. Statements get executed before the code runs

So what happens is that JavaScript will execute the var statement before before anything else, but it will not evaluate the assignment expression, therefor hiya will default to the value of undefined.

As Raynos already stated IE will execute each scripts on its own, therefore the behavior described above, will results in hiya being undefined.


Need Your Help

Rails3 - Caching in development mode with Rails.cache.fetch

ruby-on-rails-3 caching activerecord

In development, the following (simplified) statement always logs a cache miss, in production it works as expected: