1

It seemed to me, that I have understood the basic concepts of JavaScript scoping and hoisting. This question+answer helped me a lot, in that regard.

Though, recently I've come across something, that surprised me a bit. Consider the following code:

var a = 1;

if (true) {
  console.log(a);
  let a = 2;
}

console.log(a);

Given of what I have learned, I would expect it to output undefined and 1. Though it results in Uncaught ReferenceError: a is not defined.

My understanding is, that code above should be equivalent to (as declaration part of let a = 2; should be hoisted to the nearest inclosing block — which is if, in this case):

var a = 1;

if (true) {
  let a;
  console.log(a);
  a = 2;
}

console.log(a);

And this code, by the way, produces undefined and 1, as I would expect.


My question is:

  • Are variables declared with let hoisted, inside their nearest enclosing block?
    • If yes, why the code from the first block results in Uncaught ReferenceError: a is not defined?

1 Answers1

5

There is a period called Temporal Dead Zone, which affect variables declared with let and const.

Paraphrasing the this answer:

Temporal Dead Zone — is a period between entering scope and variable being declared, in which variable can not be accessed.

So, if we were to mark Temporal Dead Zone in the first code snippet, from the question:

var a = 1;

if (true) {
  /* start of the Temporal Dead Zone */
  console.log(a); /* code in the Temporal Dead Zone */
  /* last line of the Temporal Dead Zone */
  let a = 2; /* end of the Temporal Dead Zone */
}

console.log(a);

Of course, documentation, mentions this (along with other tricky cases) as well:

In ECMAScript 2015, let bindings are not subject to Variable Hoisting, which means that let declarations do not move to the top of the current execution context. Referencing the variable in the block before the initialization results in a ReferenceError (contrary to a variable declared with var, which will just have the undefined value). The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.

The excerpt above is followed by this code:

function do_something() {
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2;
}

To, explicitly, address the question: as explained in another answer — variables declared with let are hoisted, but not initialized, inside their nearest enclosing block. Which, consecutively, leads to the variable behaviour, as if they are not hoisted =)

  • 2
    That's a really weird name for a behavior that's common to multiple languages – Abhinav Gauniyal Dec 01 '17 at 08:53
  • You answered your own question. Congrats... – noChance Dec 01 '17 at 08:56
  • You prepared the answer first, then post the question. – Mamun Dec 01 '17 at 08:59
  • 2
    @AbhinavGauniyal In what other common languages is this behaving the same, and how is it called there? – Bergi Dec 01 '17 at 09:07
  • 3
    @Bergi c, c++, java.... list goes on. They all will show `variable not defined` error but its not called 'Temporal Dead Zone' in any of them since its quite obvious due to the absence of hoisting. – Abhinav Gauniyal Dec 01 '17 at 09:13
  • @Mamun, I believe 'Answer your own question' checkbox, on question asking page, intended just for [such cases](https://stackoverflow.com/help/self-answer) ;-) –  Dec 01 '17 at 09:15
  • 1
    @AbhinavGauniyal Well all of those don't have closures, and not a *temporal* dead zone but just a "no use above declaration". In JavaScript you can do `function example() { alert(x) } let x = 42; example();` where `x` is in scope above `let`. – Bergi Dec 01 '17 at 09:15
  • @Bergi there are far more differences in the semantics of languages I mentioned vs javascript so we can't actually compare their snippets fairly. The example you posted isn't doing what a corresponding c code would do. Likewise `example(); function example() {...}` is valid in js while in other languages it'll throw an error up front. Anyways I'm not criticizing js or its design choices, I just found the name a bit heavy for a behavior I found obvious in other languages I write code in. – Abhinav Gauniyal Dec 01 '17 at 09:35
  • That documentation page changed the quoted text to this: "let bindings are created at the top of the (block) scope containing the declaration, commonly referred to as "hoisting". Thought it's worth noting. – nCardot Jun 17 '18 at 23:23
  • @NatalieCardot, here is the full text of the section (as it appears now in documentation; highlights are mine): "`let` bindings are created at the top of the (block) scope containing the declaration, commonly referred to as "hoisting". Unlike variables declared with `var`, which will start with the value `undefined`, `let` variables are not initialized until their definition is evaluated. **Accessing the variable before the initialization results in a `ReferenceError`. The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.**." –  Jun 18 '18 at 08:50
  • While, quotation above states:"**Referencing the variable in the block before the initialization results in a ReferenceError (contrary to a variable declared with var, which will just have the undefined value).**" So, as far as I can see — it, essentially, the same thing expressed in other words. =) Am I missing anything? –  Jun 18 '18 at 08:52
  • 1
    Right, but from "let bindings are not subject to Variable Hoisting" I just thought people might come away thinking that let isn't hoisted (even though it's true the declaration isn't hoisted, just the binding) – nCardot Jun 18 '18 at 14:02