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:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
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.