1

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?
narra_kk
  • 111
  • 6
  • If you meant the code to work with the same variable (like it happens when you use `var var`), then you should just declare it only once (with `let`) and simply reassign it `foo = 1;` - without a second declaration. – Bergi Apr 27 '20 at 14:53

2 Answers2

2
  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

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • FWIW, I go into some detail about how bindings work, the TDZ, etc. in Chapter 2 of my new book coming out in a couple of months. If you're interested, see my profile for details. – T.J. Crowder Apr 27 '20 at 14:50
  • This makes it clear. Good explanation. But why using double var won't throw `has been already declared error`? – narra_kk Apr 27 '20 at 15:04
  • 1
    @narra_kk - Purely because `var` is defined that way in the spec, repeating `var` has no effect at all, but isn't defined as an error. The newer forms reflect a more modern approach. :-) – T.J. Crowder Apr 27 '20 at 15:24
2

When you declare a variable using let it is valid within the scope of the current code block. Your second let foo declaration defines a separate foo than the first variable and it's only valid within the if-block. However you are using it before it is defined so you get the error correctly that it's not defined yet.

If you truly intend there to be two different foo variables, I'd recommend calling them something else (foo1 and foo2 for example) to avoid the conflict. Then it becomes clear that you are using the variable before it's defined.

let foo1 = 2;
console.log(foo1);

if (foo1 === 2){
    console.log(foo1);
    let foo2 = 1;
}

If you mean for line 5 to be using the first instance of foo set to 2, then you've hidden it by the new definition happening within the if-block of code.

If you mean for the foo that's set to 1 to be used on line 5 then you should move its definition to before its use.

Note that using var has a different result because the scope of var variables his broader than the scope of let variables. See here which has this definition:

let allows you to declare variables that are limited to a scope of a block statement, or expression on which it is used, unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

To try to make it more clear I've marked your code up with the state of the variables at each stage of the code:

let foo = 2;          // foo defined and set to 2
console.log(foo);     // foo defined and set to 2

if (foo === 2)        // foo defined and set to 2
{                     // <-- start of the if-block!
    console.log(foo); // foo not defined yet
    let foo = 1;      // foo defined and set to 1
}                     // <-- end of if-block!
console.log(foo);     // foo defined and set to 2
Always Learning
  • 4,633
  • 2
  • 13
  • 30
  • As far as I understand If I use `var` as the first identifier, the `foo` variable will be defined inside of the second block right? Then why does it throw `not defined error` if I have `let foo = 1;` as line 6? – narra_kk Apr 27 '20 at 14:55
  • 1
    Because by having the declaration anywhere in the if-block makes that the variable valid in that scope and hides the one declared by the `var` even. It doesn't make it valid for just half of the block. – Always Learning Apr 27 '20 at 14:58