JS Tip of the Day: Hoisting

Hoisting
Version: ES2015 (let, const class)
Level: Intermediate

Hoisting is a term to describe how declarations in JavaScript appear to get moved or “hoisted” to the top of their scope. Hoisted variables are able to be recognized in code before their declarations. There are 3 kinds of hoisting:

  • Variables with values initialized
  • Variables with default values initialized
  • Variables with uninitialized values

Variable declarations that are hoisted with their values initialized only occurs with function declarations (including generator and async variations). This means that not only can the function name variable be recognized before its declaration, but it will also already have its function value. The result is that, during code execution, you can call a function declaration even before its declaration has been reached.

logTriangleSides(); // 3

function logTriangleSides () {
    console.log(3);
}

The effects of hoisting allows the code to appear as though it has been written as:

function logTriangleSides () {
    console.log(3);
}

logTriangleSides(); // 3

var declarations are declarations that are hoisted but initialized with a default value. The default values for such declarations (those that can have default values, i.e. var and let) is undefined. If you try to access a var-declared variable before its declaration, you’ll be able to access the variable, but you will get its default value of undefined, even if the variable was given an initializer value in its declaration. It’s defined value doesn’t get assigned until execution reaches the declaration.

console.log(squareSides); // undefined
var squareSides = 4;
console.log(squareSides); // 4

Hoisting in the context of var variables makes the code appear to be written as:

var squareSides = undefined;
console.log(squareSides); // undefined
squareSides = 4;
console.log(squareSides); // 4

Declarations that are uninitialized before their declarations are reached include let, const, and class. Unlike the other forms of hoisting seen earlier with function and var, you’re not allowed to access these variables at all prior to their declarations.

console.log(pentagonSides); // ReferenceError (pentagon has secrets!)
let pentagonSides = 5;

Hoisting here translates to something similar to:

let pentagonSides; // <-- but no initialization, not even undefined
console.log(pentagonSides); // ReferenceError
pentagonSides = 5;

Any attempt to access an uninitialized variable results in an error.

You may then ask yourself, is hoisting really involved if you can’t access the variable before its declaration? The answer is yes when you consider the following example:

let pentagonSides = 'five';
{
    console.log(pentagonSides); // ReferenceError
    let pentagonSides = 5;
}

Here there are two scopes, the top-level scope where the first pentagonSides is defined with a value of ‘five’, and then a block-level scope below it with a call to console.log followed by the original pentagonSides declaration from before. Even though the first pentagonSides variable would have been initialized and available before the call to console.log, an error is still thrown because the pentagonSides that call is recognizing is the uninitialized version declared below it; declared below, but hoisted (though uninitialized) above.

let pentagonSides = 'five';
{
    let pentagonSides; // <-- uninitialized
    console.log(pentagonSides); // ReferenceError
    pentagonSides = 5;
}

This form of hoisting creates what is referred to as the “temporal dead zone”, or TDZ. The temporal dead zone is the area in the scope above let, const, and class declarations where those variables cannot be accessed. This applies even though there might be a variable of the same name accessible from a higher scope.

More info:

1 Like