Reputation: 2737
I was playing with some code and ran into a situation where I couldn't identify why let
is behaving the way it does.
For the below block of code:
var x = 20; // global scope
function f() {
let x = x || 30;
}
f(); // VM3426:1 Uncaught ReferenceError: x is not defined(…)
I get the error “x is not defined” on executing f()
. I do understand let
variables are not hoisted but since x
has a global copy, why isn't the line inside function f
defaulting to global copy instead of throwing an error?
Does let
set the variable to undeclared (instead of 'undefined' with var
because of hoisting) at the beginning of the function?
Is there any way to get the global copy of x
within the function?
Upvotes: 6
Views: 1508
Reputation: 27239
Though in JavaScript block-scoped const
and let
declarations aren’t hoisted to the top of the function, they are still hoisted to the top of the block. As such, once a block is entered, all bindings scoped to that block immediately become available, even those that have not yet been initialized. Accessing a binding between entering its scope and initializing it is a runtime error: the time when this can happen is known as the temporal dead zone. The expression initializing a block-scoped binding is within its temporal dead zone; as such, using the binding identifier in its initializing expression will throw an error.
Although I also sometimes find this design decision disappointing, there are some advantages to it. For example, it allows closures to refer to each other without the need for forward declarations:
const flip = n => {
if (n > 0) {
console.log("flip");
return flop(n - 1);
}
};
const flop = n => {
if (n > 0) {
console.log("flop");
return flip(n - 1);
}
};
flip(5);
But it does mean that capturing a previous binding in the initializing expression of a shadowing binding, like let x = 42; { const x = x; }
, is impossible.
To access specifically a global binding, you could explicitly refer to a property of a global object, through globalThis
, window
or such:
var x = 20; // global scope
function f() {
let x = globalThis.x || 30;
return x;
}
console.log(f()); // 20
x = void 0;
console.log(f()); // 30
However, for shadowing outer bindings in general, you will have to resort to an awkward contraption:
{
let x = 20; // outer, but not global scope
console.log(globalThis.x); // undefined (see?)
function f() {
let outerX = x;
{
let x = outerX || 30;
return x;
}
}
console.log(f()); // 20
x = void 0;
console.log(f()); // 30
}
Upvotes: 0
Reputation: 1297
The declaration of the local x is shadowing the global one and new one is still not declared first and cannot be used during initialization. It is same situation like following :
var foo = 5;
function do_something() {
console.log(foo); // ReferenceError
let foo = 2;
}
do_something();
For reference MDN
As suggested by @m.antkowicz you can use window.x
in your case.
Upvotes: 0
Reputation: 1363
you can use this keyword to access global scope
var x= 20; // global scope
function f() {
let x =this.x || 30;
}
f();
but In strict mode this will default undefined
Read it https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/this
Upvotes: -1
Reputation: 13581
The exception is about the right side x - when you are initializing block-scope x variable the global one is already "forgotten" however the new one is still not being declared and cannot be used during initialization
Compare with explicit calling global one
function f() {
let x = window.x || 30;
}
Also take a look at this MDN article about let dead zones
Upvotes: 6
Reputation: 1799
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
Here in your case, an exception is thrown because you are defining let with the same name.
Upvotes: -1