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?
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 var
s 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.😮