Left ArrowBack

notes / JavaScript / scopes / hoisting

hoisting

A chain hoist

JavaScript Hoisting

Planted

Status: seed

If you are unfamiliar with the terminology below, check out:

An identifier is a sequence of characters in the code that identifies a variable, function, or property. For example: the myVar string in var myVar = 1. Every time the compiler enters a scope, it starts registering declarations. Each identifier will be moved to the top of a scope. This is known as hoisting. What scope the identifier moves to the top of depends on the type of declaration:

  • function,
  • var or
  • let, const & class.

M

etaphor:

Imagine being a kid & having a messy room. You can put things wherever you like. But, when a parent comes in, they move everything. Socks moved to the socks draw. Dirty clothes thrown in the washing basket. Toys moved to the shelves...

Your room is like the scope, items are identifiers & your parents are the compiler. When you code something, you can put identifiers wherever you like in the scope. But, when the compiler enters the scope, it moves them to their proper place. This is hoisting.

function

A function declared identifier will move to the top of the nearest block scope. If none available, the nearest parent scope. It will also be initialized to its associated function reference & can be used. Below is an example of calling a function before it is declared. It works because it is hoisted to the top of the global scope by the compiler before line: 1 is executed.

/index.js

Console

func()

function func() {
   console.log('called')
}

This only applies to formal functions (functions declared with the function keyword), not function expressions:

  • const func = () => { .. } or
  • const func = function() { .. }.

var

A var declared identifier will move the top of the nearest function scope. If none available, the global or module scope. It will also be initialized to its default value of undefined & can be used. Even if you declare & initialize the variable on the same line, var myVar = 1, only the identifier is hoisted. The variable will be initialized to its correct value when the line of code is executed.

Code showing the steps of the compiler hoisting a variable

/index.js

Console

console.log(myVar)
var myVar = 1;
console.log(myVar)

Below is an example of a variable declared & initialized in a block scope. Because it's declared as a var, it is hoisted to the function scope. This makes it accessible to the console.log.

/index.js

Console

function myFunc(x) {
   if (x === 1) {
      var y = 1
   }

   console.log(y)
}

myFunc(1)
A biohazard warning sign.

const, let & class

A const, let or class declared identifier will move the top of the nearest block scope. If none available, the nearest parent scope. It will also be initialized to its default value of uninitialized. It will remain uninitialized until the compiler executes the line where is it declared. Attempting to use it before then results in an error.

/index.js

Console

console.log(myVar)
let myVar = 1;

TDZ

The above is an example of The Temporal Dead Zone. A window of time where a variable is declared but uninitialized. When a var is hoisted, it is initialized with a default value of undefined, so it has a TDZ of 0 in length & unobservable. let, const & class have an observable TDZ.

Code showing the steps of the compiler entering & exiting the TDZ

/index.js

Console

console.log(myVar)
let myVar = 1

TDZ errors can be avoided by always putting let, const & class declarations at the top of their scope. This shrinks the TDZ to 0 length. If the declaration isn't needed from the beginning, create an inner block scope:

Instead of this:

/index.js

function add1IfEven(x) {
   if (!x) {
      throw new Error("Input required")
   }

   const isEven = x%2 === 0
   return isEven ? x + 1 : x
}

Do this:

/index.js

function add1IfEven(x) {
   if (!x) {
      throw new Error("Input required")
   }

   {
      const isEven = x%2 === 0
      return isEven ? x + 1 : x
   }
}
Judge Dredd

Rules of Scope

Rules of scope, such as hoisting, are applied per scope instance. During execution, each time a scope is entered, everything resets. This is why using a const within a loop doesn't throw aa already declared error after the 1st iteration.

/index.js

Console

for (let i = 0; i < 2; i++) {
   const myVar = i
   console.log(myVar)
}

Where to Next?

A sci-fi robot taxi driver with no lower body