RunJS LogoRunJS

How hoisting in JavaScript works

Published on

JavaScript hoisting enables functions and variables to be used before they are declared. This behaviour has a few nuances that will be explained further in this article, along with examples of how to avoid potential errors that can occur as a result.

When a function or variable is hoisted in JavaScript, it means that it is possible to access it at the top of its containing scope. This can be either the global scope or the local scope. The following two examples demonstrate hoisting at it's most basic:

console.log(foo); // Output: undefined
var foo = "Hoisted variable";

In the above example, the JavaScript interpreter has split the variable declaration and assignment and, in effect, pulled the declaration to the top of the scope. The interpreter also initializes the variable with the value of undefined, which is the result that gets output at the point that it is logged.

foobar(); // Output: Hoisted function
function foobar() {
console.log("Hoisted function");
}

In the case of the above function, the javascript interpreter has again pulled the declaration to the top of the scope. As this is a function declaration, not an assignment, the function can be called and provide a result before it's declared. The reason behind this behaviour is because the javascript interpreter interprets code in two stages:

  • Creation Stage: This stage creates all the scopes, variables and functions. It also sets the 'this' context.
  • Execution Stage: This stage executes the code by sending machine understandable commands. Now, let's go more in-depth and look at variable and function hoisting with some more examples.

Variable hoisting

Let's start by looking at some other types of variables. In addition to var we also have let and const. These don't behave the same way as var. The following examples show what happens when these types of variables are called before their declaration:

console.log(a); // ReferenceError: a is not defined
let a = "a";
console.log(b); // ReferenceError: b is not defined
const b = "b";

In both cases, calling variables declared with either let or const before they are declared will result in a reference error being thrown. This is because, while the interpreter is hoisting these too, it is not initializing them and this is why calling them results in an error.

Another scenario to look at is how variables are hoisted within functions. The following example shows what happens when a var declared inside a function is called before its declaration:

console.log(bar); // ReferenceError: bar is not defined
function foo() {
console.log(bar); // Output: undefined
var bar = "bar";
}
foo();

The var is hoisted to the top of the function and initialized with a value of undefined, but this does not mean its value is accessible outside the function. An error is thrown when trying to access the variable outside the function. This behaviour is different when using other variable types such as let and const.

console.log(bar); // ReferenceError: bar is not defined
function foo() {
console.log(bar); // ReferenceError: bar is not defined
let bar = "bar";
}
foo();

The let declared inside the function is not accessible outside of the function as a reference error is thrown when calling it, and the same happens when accessing it within the function before it's declared. This behaviour is also consistent when declaring the variable with const.

Function hoisting

We saw earlier that function declarations are hoisted to the top of their scope. Now let's take a look at function expressions.

expression(); //Output: "TypeError: expression is not a function
var expression = function () {
console.log("Function expression");
};

In the above example, calling the function before it has been declared results in a type error being thrown. This is because, while the variable is hoisted to the top of its scope, it's not initialized with a function value and so trying to call it results in an error. This behaviour is consistent when using arrow functions as well:

expression(); //Output: "TypeError: expression is not a
var expression = () => {
console.log("Function expression");
};

Order of precedence

When declaring variables and functions, it's important to consider the order of precedence as this will change the behaviour. The following example shows what happens when a var and a function declaration with the same name are declared in the global scope:

var example = "I am a variable";
function example() {
return "I am a function";
}
console.log(typeof example); // Output: string

The example variable remains a string because variable assignment takes precedence over function declaration. In the following example, the variable assignment is removed, but the variable is still declared before the function:

var example;
function example() {
return "I am a function";
}
console.log(typeof example); // Output: function

Now, the example variable is of type function, and this is because function declarations take precedence over variable declarations.

Conclusion

Hopefully, you now have a good understanding of how hoisting works in JavaScript. Keep in mind that hoisting only moves declarations to the top of their scope and does not initialize variables with values. It's important to be aware of this behaviour as it can lead to unexpected results if not considered. When declaring functions and variables, take into account the order of precedence as this will change the outcome.

Join the RunJS mailing list

Recent posts