Search code examples
javascriptnode.jssyntax-erroridentifierreferenceerror

How is it possible that lines in the end (which were not executed yet) affect the beginning of the code? And why does it throw an incorrect error?


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:

  1. How is it possible that line 6 (which hasn't been executed yet) makes line 5 end up with an error?

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.

  1. Why does it throw 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:

  1. Why is everyone against of using var when in this scenario double using var is the only way the code works flawless? Is it an exception?

Solution

    1. 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.

    1. 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