Hoisting in JavaScript

Introduction

Hoisting is a JavaScript behavior commonly known for making variables and functions available for use before the variable is assigned a value or the function is defined. In effect, it puts variable, function and class declarations to the top of their scope (the global scope or a function) before execution.

In actuality, JavaScript does not move or add code to hoist declarations. These declarations are put into memory during the compile phase of the interpreter - making them available before the code is executed.

In this article, we'll learn about hoisting and how it affects variables, functions and classes.

Hoisting Variables

One of the key aspects of hoisting is that the declaration is put into memory - not the value.

Let's see an example:

console.log(name); // Prints undefined, as only declaration was hoisted
var name = "John";
console.log(name); // Prints "John" 

This happens because JavaScript sees that we have a variable name in the scope and puts it into memory. Variables declared with var are given a value of undefined until they are assigned something else.

Variable Hoisting with let and const

JavaScript developers seldom use var in favor of the let and const keywords introduced in ECMAScript 2015 (commonly referred to as ES6). Variables declared with let and const are hoisted. However, they are not initialized with undefined, or any value. Therefore, if they are used before they initialized, we will get a ReferenceError.

Let's re-use the same example, but use let instead of var:

console.log(name); // Uncaught ReferenceError: Cannot access 'name' before initialization
let name = "John";
console.log(name);

The above code throws a ReferenceError in the first line and ends execution.

The const keyword was added to introduce immutable values in JavaScript - values which cannot be changed after it was initialized. As such, when using const we must declare and assign a value to the variable. If we do not, we'll get a SyntaxError:

console.log(name); // Uncaught SyntaxError: Missing initializer in const declaration
const name;

And like let, we get a ReferenceError when we try to use a constant before it was initialized:

console.log(name); // Uncaught ReferenceError: Cannot access 'name' before initialization
const name = "John";
console.log(name);

Hoisting Functions

Function declarations are hoisted in JavaScript. A function declaration begins with the keyword function, followed by its name and arguments in brackets, and then its body. Let's consider the following code:

let first_name = "Stack";
let last_name = "Abuse";

let result = concat(first_name, last_name);
console.log(result); // StackAbuse

function concat(x, y) {
    return x + y;
}

As with variables, JavaScript puts the function into memory before executing the code in that scope. Therefore, hoisting allows us to call the concat() function before it is defined later in the code.

While function declarations are hoisted, function expressions don't work in the same way. A function expression is when we assign a variable to a function. For example, the code below will return an error:

func_express(); // TypeError: func_express is not a function

var func_express = function () {
    console.log('This the function expression is not a function');
};

JavaScript returns a TypeError because unlike function declaration, only the variable was hoisted. When variables declared with var are hoisted, they are given a default value of undefined. JavaScript then throws an error because the value of the variable is not a function at that point in time.

Function Hoisting with Arrow Functions

ECMA2015 introduced a new way to create anonymous functions, arrow functions. These are defined by a pair of brackets containing 0 or more arguments, an arrow => and the function body in curly braces. With regard to hoisting, they operate as other function expresses. For example:

arrow_func_express(); // TypeError: arrow_func_express is not a function

var arrow_func_express = () => {
    console.log('This the arrow function is not a function');
};

When using arrow functions, or any other function expression, we must always define the function before we use it in our code. This is the correct way to use a function expression:

let arrow_func_express = () => {
    console.log('This function expression will work');
};

arrow_func_express(); // This function expression will work

Hoisting Classes

Class declarations are hoisted in JavaScript. A class declaration is uninitialized when hoisted. That means, while JavaScript can find the reference for a class we create, it cannot use the class before it is defined in the code.

Let's take the following example which throws an error for trying to access a class before its definition:

var person = new Person();
person.name = "Jane";
person.age = 25;

console.log(person); // Uncaught ReferenceError: Cannot access 'Person' before initialization"

class Person {
    constructor(name, age) {
      this.name = name; this.age = age;
    }
}

The ReferenceError is similar to what happens when we try to access a variable declared with let or const before it is initialized in our script.

Class expressions, where we assign a class definition to a variable, behave similarly to function expressions. Their declarations are hoisted but not their assigned value.

Let's take our previous example and use a class expression instead:

var person = new Person();
person.name = "Jane";
person.age = 25;

console.log(person); // Uncaught TypeError: Person is not a constructor"

var Person = class {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }
}

When the variable Person is hoisted, it is given the value undefined. Since we cannot use an undefined value as a class, JavaScript throws a TypeError.

When working with class declarations or class expressions, we should always define the class earlier in our code to use it. The correct way to use a class is as follows:

class Person {
    constructor(name, age) {
      this.name = name; this.age = age;
    }
}

var person = new Person();
person.name = "Jane";
person.age = 25;

console.log(stack); // Person { name: 'Jane', age: 25 }

Conclusion

JavaScript hoists, variable, function and class declarations. That article has shown the impacts hoisting has on how we write JavaScript code.

Function declarations can be used before their definition because of hoisting. However, to minimize our chances of getting undefined values as well as reference or type errors, it is safer to use variables, function expressions and classes after they are defined in our code.