So I had an x is not defined
error in my code which confused me a little bit because x
was already defined couple lines before. I had to spend some time tweaking my code, deleting and adding lines until I managed to understand why it was happening. After I removed every unnecessary information, now the code looks like this:
let foo = 2;
console.log(foo);
if (foo === 2){
console.log(foo);
let foo = 1;
}
It throws foo is not defined
at line 5. An error pops out when I'm trying to console.log(foo)
! If I remove line 6 let foo = 1;
the code works fine. I mean an error happens before I declare foo
for a second time. So the first question is:
The second thing I can't understand is why does it say foo is not defined
instead of foo has been already declared
.
If I replace the second let
with var
an error will appear at line 6 and it will say foo has been already declared
so it looks fine. But having let
being set as the second identifier always throws an incorrect error.
After testing different scenarios I noticed that the outcome depends on which identifiers I use:
identifiers | result
----------------------------------------------
var var | the code works well
var let | not defined error
let var | has been already declared error
let let | not defined error
So the 3rd question is:
var
when in this scenario double using var
is the only way the code works flawless? Is it an exception?
- How is it possible that line 6 (which hasn't been executed yet) makes line 5 end up with an error?
Because the scope of bindings (loosely, "variables") declared with let
, const
, and class
is the entire block, not just from where they're declared to the end of the block. The time between code entering the block and the execution of the let
statement is called the Temporal Dead Zone (TDZ), during which the binding exists but is uninitialized and cannot be used in any way. Just having let foo
in the block shadows the outer foo
, even before the let foo
is encountered in the code flow.
Aside from scope, this TDZ is the big difference between var
and let
is that var
creates a binding and initializes it to undefined
, regardless of where the var
statement is in the scope. In contrast, let
(and const
and class
) create the binding, but don't initialize it until later, when the let
(const
, class
) is encountered in the step-by-step execution of the code. You can't use an uninitialized binding.
- Why does it throw an incorrect error?
It's not incorrect. You could argue it's poorly-worded. :-) Basically it's saying "you can't use foo
here, it's not initialized." The current error message from V8 (the JavaScript engine in Chrome, Chromium, Brave, the new Chromium-based Edge, and Node.js) is, to my mind, clearer:
Uncaught ReferenceError: Cannot access 'foo' before initialization