Search code examples
javascriptshadowing

Illegal Shadowing (Shadowing let with var)


Consider the following example of shadowing in JavaScript:

let a = 99;                                                                                                             
{
        var a = 10;
        let b = 11;
        const c = 200;
        console.log(a);
}
console.log(a);

Here I get the following error:

SyntaxError: Identifier 'a' has already been declared

But in the following case, there is no syntax error and the code is completely valid.

var a = 99;                                                                                                             
{
    let a = 10;
    let b = 11;
    const c = 200;
    console.log(a);
}
console.log(a);

In the second case var a is declared in global scope and let a is in block scope. But why this is not valid in first case? There, let a will be declared in separate scope and var a should be declared in the global scope. Why this case is invalid?


Solution

  • But why this is not valid in first case?

    Because both of those declarations are in the same scope.¹ Your second example works because the let is scoped only to the block, not to the scope outside the block where var a exists. (Just like a local variable in a function.) So in the second example, it's shadowing. In the first example, it wouldn't be shadowing, it would be duplication, which you can't do with let (even if the other declaration uses var). (You can use var to declare a variable more than once in the same scope. The subsequent vars are ignored, though any initializer on the variable is converted to an assignment. E.g., var a; var a = 42; is identical to var a; a = 42;)


    ¹ Okay, so technically, it's a bit more complicated than that. var variables exist in an "outer" global lexical environment that holds bindings (loosely, variables) on the global object, but let variables exist on an "inner" global lexical environment that doesn't hold its bindings on the global object. Global scope is still just one scope, so as you've seen you can't have var a and let a both at global scope because they conflict with each other. But there are other ways to create "outer" global variables, such as assigning to a property on window (or this at global scope, or globalThis in modern environments):

    // At global scope
    window.a = 42;    // Creates binding in the outer global environment
    console.log(a);   // Shows 42
    

    which means it's possible to create a binding in both the outer and inner global lexical environments:

    // At global scope
    window.a = 42;    // Creates binding in the outer global environment
    let a = "answer"; // Creates binding in the inner global environment
    console.log(a);   // Shows "answer"
    

    Since the inner one is closer to the code doing the console.log(a), the above outputs "answer".

    Fun fact: Depending on how you count, on browsers there are somewhere between two and six layers to "the" global environment.😮