1

I am struggling with this piece of code:

let foo = 'bar';
{
  let foo = 'qux';
}

console.log(foo);

The text accompanying this code says:

What does this program log to the console? Why?

Solution: The program logs bar.

Line 1 initializes a variable named foo with the value'bar'. Line 2 starts a block, which creates a new scope for let variables. The variable on line 1 is still visible at this point, but line 3 declares a new variable named foo that shadows (hides) the variable from line 1. This second variable gets initialized to 'qux', but it goes out of scope on line 4 when the block ends. That brings foo from line 1 back into scope, so line 6 logs its value: bar. Source: https://launchschool.com/books/javascript/read/variables#variablesandvariablenames

My expectation was that the global variable foo would be visible from inside the code block and still equal to 'foo' before it is redeclared. I checked the output of foo before and after redeclaration to find out if I was right:

let foo = 'bar';

{
    console.log(foo); // should be 'bar', but throws an error: VM786:4 Uncaught ReferenceError: Cannot access 'foo' before initialization
    let foo = 'qux';
    console.log(foo); // should be 'qux'
}

console.log(foo); // should be 'bar'

The code throws an error when run in the console. When I step through it in the debugger, the local foo is 'undefined' in the first line of the code block, while I expected it to still be "bar' (until it is redeclared).

To find out, I read about the lexical environment in JavaScript:

When the script starts, the Lexical Environment is pre-populated with all declared variables. Initially, they are in the “Uninitialized” state. That’s a special internal state, it means that the engine knows about the variable, but it cannot be referenced until it has been declared with let. It’s almost the same as if the variable didn’t exist. Source: https://javascript.info/closure

My assumption: The above process also happens when a new code block is started (not just when a whole new script is run): The variables declared inside it are added to the lexical environment of the block, but cannot be referenced yet.

Is this a correct description of what is happening here? Is it the reason why foo is not 'bar' in the first line of the code block?

Thank you so much!

Julia
  • 21
  • 2
  • Doesn't your citation from https://javascript.info/closure exaclty explain what's happening? I mean you you explained it all yourself? :-D – Felix Sep 03 '22 at 09:33
  • There are two aspects to the rules for lexically-scoped declarations (like `let`; **not** like `var`): 1. It's scoped to the block, not the function or global context. 2. It's *hoisted* (takes effect from the top of the block) but not *initialized* until it's reached in the step-by-step code. The time between execution entering the block and execution reaching the declaration is called the Temporal Dead Zone; you cannot access the variable *at all* in the TDZ. FWIW, I wrote this up in [this answer](https://stackoverflow.com/a/43292418/157247) to a dupe of the linked question. – T.J. Crowder Sep 03 '22 at 09:42
  • @Felix I tried to explain it, but I wasn't sure if I was right so I wanted to check with people who know more than me! :) – Julia Sep 03 '22 at 17:30
  • @T.J.Crowder Hi T.J., thank you so much, this is really helpful! I had never heard about `let` and `const` being hoisted, I thought that only applied to function declarations. I learned something new thanks to you! :) – Julia Sep 03 '22 at 17:32
  • @Julia - :-) No worries. `class` is also hoisted (or half-hoisted) like `let` and `const` are. `var` is hoisted *and* initialized (with the value `undefined`). So if you have `console.log(blah); var blah = 42; console.log(blah);` you don't get an error like you would with `let`, you see `undefined` and then `42`. (`var` shouldn't be used in new code.) Happy coding! – T.J. Crowder Sep 04 '22 at 07:16

0 Answers0