Search code examples
javascriptscopeecmascript-6varlet

Javascript: how let is hoisted (or not) inside if block?


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?

Solution

  • 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 =)