DEV Community

mmvergara
mmvergara

Posted on

Javascript Hoisting - What Moves and What Stays

Think of hoisting like setting up a stage before a play. Before the code runs (during the "creation phase"), JavaScript moves all declarations to the top of their scope like stagehands moving props into place before the curtain rises. The only catch is that the declarations are moved, not the initializations.

According to the ECMAScript specification, this behavior is actually part of how JavaScript creates what's called the "lexical environment" during the creation phase. But let's not get too technical just remember that JavaScript does a "pre-scan" of your code before running it.

var Hoisting

console.log(x); // Outputs: undefined
var x = 5;
console.log(x); // Outputs: 5
Enter fullscreen mode Exit fullscreen mode

In this example, the var declaration var x is hoisted to the top of the scope, but the initialization x = 5 is not. This is why the first console.log(x) outputs undefined instead of throwing an error. The variable x exists, but it hasn't been assigned a value yet.

let and const in the Temporal Dead Zone (TDZ)

console.log(y); // Throws a ReferenceError: Cannot access 'y' before initialization
let y = 10;
console.log(y); // Outputs: 10
Enter fullscreen mode Exit fullscreen mode

In this example, the let declaration let y is hoisted, but it is placed in the Temporal Dead Zone (TDZ) until the line where it is initialized. Trying to access y before its initialization results in a ReferenceError. This behavior is different from var, which would simply return undefined in a similar situation.

this also includes anything that is declared using let or const including functions ofcourse

The TDZ (Temporal Dead Zone)

Speaking of the temporal dead zone, this is where let and const declarations live before they're initialized. Unlike var which returns undefined when accessed before initialization, these modern declarations will throw an error. The ECMAScript spec calls this behavior "temporal dead zone semantics", but I just call it "JavaScript keeping us honest".

A slightly harder example

function setupEventHandler() {
  handleClick(); // Works!

  const config = {
    debug: true,
  };

  function handleClick() {
    if (config?.debug) {
      // Undefined!
      console.log("Debug mode");
    }
  }
}

setupEventHandler();
Enter fullscreen mode Exit fullscreen mode

See what happened there? The function declaration handleClick is hoisted, so we can call it early. But that object config? It stays right where it is. This is why accessing config inside handleClick gives us undefined, we're trying to read the script before its ready.

The Class Gotcha

Class hoisting doesn't work quite the same way.

const dog = new Animal(); // Throws an error!

class Animal {
  constructor() {
    this.type = "mammal";
  }
}
Enter fullscreen mode Exit fullscreen mode

While classes are hoisted, they stay in a "temporal dead zone" until their definition is evaluated. This means you can't access them before they're declared.


Just remember declarations are moved to the top, but initializations stay put.

Top comments (0)