javascript - Online Learning - Programming Languages - Tech tutorial

Hoisting in JavaScript

In JavaScript, hoisting refers to the built-in behavior of the language through which declarations of functions, variables, and classes are moved to the top of their scope – all before code execution. In turn, this allows us to use functions, variables, and classes before they are declared.

In this post, we’ll explore a few examples of hoisting, as well as take a look at the impact that hoisting has on the code we write.

Declarations.

Before we begin, let’s make sure we’re on the same page regarding declarations. In JavaScript, declarations include using:

  • function to declare a function (including generators)
  • async function to declare an async function (including generators)
  • class to declare a class
  • var to declare a variable
  • let to declare a block-scoped variable
  • const to declare a block-scoped, non-reassignable variable

Consider the following examples:

// Declaring a function
function myFunction() {
  // ...
}


// Declaring a variable
let a = "a";


// Declaring a class
class MyClass {
  // ...
}

In the above snippet, we are simply making three declarations: a function, a variable, and a class. Now, what does it mean to hoist these declarations?

Function hoisting.

Under the hood, function declarations are put into memory at compile time. As such, this makes it possible to, on execution, call a declared function before it is defined. For example:

console.log(greeting()); // "Hello there!"


function greeting() {
 return "Hello there!";
}

In the above snippet, we are able to log the return value of invoking greeting() before that function is even defined. This is all due to function hoisting – a built-in behavior of JavaScript that hoists a function’s declaration and its value to the top of their scope.

Note that this differs from invoking a function that is never declared:

console.log(greeting()); // ReferenceError: greeting is not defined

Since the function is never defined anywhere, the JavaScript interpreter evaluates the expression as a ReferenceError.

Now that we’ve seen how JavaScript hoists functions, let’s take a look at how variables are hoisted.

Variable hoisting with var.

Unlike function hoisting, variable hoisting shows slightly different behavior. When a variable is declared with the var keyword, the JavaScript interpreter still hoists its declaration. However, it differs from how functions are hoisted in that the value of the variable (if it has one) is not included. Consider the following example:

console.log(a); // undefined

var a = "a";

console.log(a); // "a"

When the variable a is hoisted, the JavaScript engine still moves it to the top of its scope – but it initializes the variable with a value of undefined. As such, until a is actually declared on the next line, its actual value won’t be assigned. Then, after declaration, we can finally log its value to the console: “a“.

Now, what about variables declared with the let and const keywords? As it turns out, there are some slightly different behaviors declaring block-scoped variables as well.

Variable hoisting with let and const.

Similar to declaring a variable with var, the declaring a variable with let or const still sees the variable declaration hoisted to the top of its scope. However, rather than initializing that variable with a value (i.e., undefined, as shown above), the JavaScript engine forgoes that step entirely. As such, by not being initialized, the JavaScript interpreter will throw an error if we try to access the variable:

console.log(a); // ReferenceError: Cannot access 'a' before initialization

console.log(b); // ReferenceError: b is not defined

let a = "a";

console.log(a); // "a"

The reason why we see these errors, even though variable a is properly hoisted, is due to the variable being in the temporal dead zone (TDZ). That is, while the variable is in scope, we cannot access or use the variable at all before it is explicitly declared. This same behavior also applies when declaring a class with the class keyword:

let instance = new MyClass(); // Cannot access 'MyClass' before initialization

class MyClass {
 // ...
}

Certain developers in the community may argue that declarations with let, const, or class are non-hoisting in the most strict sense.

Now that we’ve seen how different declarations can be hoisted, what significance does this have in code?

To hoist or not to hoist?

Hoisting is not explicitly defined in the ECMAscript specification, which describes the standards of JavaScript as a language. For the most part, you should consider hoisting as more of a default behavior of JavaScript rather than a feature. That is, rather than leveraging hoisting as a practical tool or design pattern in the scripts and applications you write, you should just be mindful of its characteristics as you continue to work with JavaScript.

In certain code bases, for example, you might come across functions being used in business logic at the top of the script, while its declarations are left at the bottom of the script (something that AirBnb’s JavaScript Style Guide advises against). Though less common and probably only partially useful, know that this is all possible due to hoisting.

Learn more about hoisting.

For more information about hoisting, declarations, and scope in JavaScript, check out these resources:

Or explore the Intermediate JavaScript Nanodegree program to master the most popular programming language in the world.