Javascript

A deep dive into Hoisting in JavaScript

Hoisting is a term used in JavaScript to describe the processing of variables and function declarations during the compilation phase of the...

Written by Luci · 7 min read >

Hoisting is a term used in JavaScript to describe the processing of variables and function declarations during the compilation phase of the code, which occurs before the code is executed.
Before starting let’s have a look at this code.

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

This is more or less what you’d expect. An error is thrown when you try to access a variable that does not exist. But what about this case?

console.log(tempStr);
// Outputs: undefined
var tempStr = “Hello World!!”;
console.log(tempStr);
// Outputs: “Hello World!”

So, what’s happening here?

Variables can be accessed before they are declared in the code when using the var keyword. This behaviour may seem strange to programmers who are used to other programming languages. This is because all var declarations are “hoisted” or moved to the top of the current scope during the compilation phase.

In JavaScript, hoisting affects the life cycle of variables, which involves three steps:

  1. Declaration — creating a new variable using a keyword like let, e.g., let myValue.
  2. Initialisation — assigning an initial value to the variable, e.g. myValue = 150.
  3. Usage — accessing and utilising the variable value, e.g. alert(myValue).

Hoisting refers to JavaScript’s behaviour of automatically moving variable and function declarations to the top of their respective scopes, regardless of where they are defined in the code.

While this feature can be helpful in some cases, it can also lead to confusion if not properly understood.

We are going to discuss three types of hoisting in this blog post:

  1. variable hoisting
  2. function hoisting
  3. class hoisting.

After reading this blog, I hope you have a better understanding of hoisting and how it impacts your code.

So, let’s get started!

Variable hoisting

Whenever a variable in JavaScript is declared with the “var” keyword, it is automatically moved to the beginning of its scope, which can be the global scope or the scope of a function.

However, it is important to note that only the declaration of the variable is moved to the top of the scope, not its initialization. That is, if you declare a variable and initialise it later in the code, the variable is undefined until it is assigned a value.

For example, consider the following code:

console.log(myVariable); // undefined
var myVariable = 10;

Although this code will not throw an error, it will log “undefined” to the console because the declaration of “myVariable” is hoisted to the top of the scope, but its initialization is not.
To avoid such issues, it is best practice to declare and initialize variables at the same time.

One scenario that might be a typical trap for new JavaScript developers is when they use the same variable names in both an inner and outer

Take the following example:

var name = "Lalit";

(() => {
console.log(My name is ${name}); // Outputs: "My name is undefined"
var name = "Ankit";
console.log(`New name is ${name}`); // Outputs: "My name is Ankit"
})();

Here, the developer may have expected that the variable “name” would hold its value from the outer scope until it was declared in the inner scope. However, due to hoisting, the variable is undefined at that point, which can be a source of confusion and errors.

So, it’s important to keep in mind that hoisting only works for declarations and not initializations.

ES6, the latest version of JavaScript, introduced two new keywords, “let” and “const”, which offer better scoping options than the old “var” keyword. Unlike variables declared with “var”, variables declared with “let” and “const” are block-scoped, meaning that they exist only within the block of code where they are declared, and not in the outer scope.

However, one key difference between “var” and “let”/”const” is how hoisting works.

“let” and “const” variables are not hoisted to the top of their scope, and accessing them before they are declared will result in a reference error. This is because they are in the Temporal Dead Zone (TDZ), which is the scope between the start of the block and the initialization line of the variable.

Using “let” and “const” instead of “var” can help avoid potential bugs in your code, as it forces you to declare and initialise your variables before using them. This makes your code easier to understand and less confusing for others who may read it.

So, it is usually a good idea to use “let” and “const” in your code as it helps to catch bugs early.

Example:

console.log(myVariable); // ReferenceError: myVariable is not defined
let myVariable = 10;

In the above code, the variable “myVariable” is declared using “let”, which means it is not hoisted to the top of the scope. When the code tries to log the variable before it is declared, a reference error is thrown.

Function hoisting

It is possible to call a function before it has been declared in the code because in JavaScript, function definitions are hoisted to the top of their scope. Not just the name, but the entire function declaration — including its body and parameters — is relocated to the top of the scope. As a result, your source code will be easier to read and understand because your high-level logic will be expressed at its beginning rather than its conclusion.

Example:

foo(); // "Hello, world!"

function foo() {
console.log("Hello, world!");
}

The function “foo” is called in the above code sample before it is declared, yet it still functions flawlessly since the full function declaration is hoisted to the top of the scope. As anticipated, the console will display “Hello, world!”

It’s important to note that the hoisting of function definitions only happens for function declarations and not for function expressions or arrow functions.
In a function expression, a function is assigned to a variable, and it must be defined before it’s used. Hence, calling a function expression before its definition is not possible.
So, it’s a good practice to declare and define functions before they are used to avoid potential errors.

Example:

sayHiHoisted(); // Hi

function sayHiHoisted() {
console.log('Hi');
};

sayHiNotHoisted(); //Output: "TypeError: sayHi is not a function

var sayHiNotHoisted = function() {
console.log('Hi');
};

In the code example above, we can see the interaction of two different types of hoisting.

First, the function sayHiHoisted() is called before it is declared in the code. But because function declarations are hoisted to the top of their scope, the entire function is moved to the top of the scope, making it callable. Hence, the output is “Hi.”

However, when we try to call the function sayHiNotHoisted(), which is defined using a function expression, we get a “TypeError.” This is because only the declaration of the variable is hoisted to the top of its scope, and not its function definition. Therefore, the variable is undefined at the time of the function call.

You may wonder what happens when using a named function expression.

funcName(); // ReferenceError: funcName is not defined
varName(); // TypeError: undefined is not a function

var varName = function funcName() {
console.log("Definition not hoisted!");
};

In the example above, the function’s name “funcName” doesn’t get hoisted if it is part of a function expression. As a result, when trying to call the function using the name, a “ReferenceError” is thrown. On the other hand, a “TypeError” is raised when attempting to call the function using the variable name because the function has not yet been declared.

Order of Precedence:

In JavaScript, there are rules that determine the order of precedence when it comes to variable and function declarations. These rules can affect the outcome of your code and it’s important to understand them.

  1. A variable assignment takes precedence over a function declaration. This means that if a variable is assigned with a value and later a function with the same name is declared, the variable assignment will take precedence and overwrite the function.
  2. Variable declarations come after function declarations in the order of priority. When a function and a variable with the same name are declared in the same scope, the function will take precedence. and the variable will be ignored.
  3. Function declarations are hoisted over variable declarations, but not over variable assignments. This means that function declarations are moved to the top of their scope, but variable assignments are not. Therefore, if a variable is assigned a value before the function declaration, the function will still be hoisted but the variable assignment will take precedence.

Let’s see how this plays out in code:

Example 1: Variable assignment over function declaration

var square = 10;

function square(num) {
return (num*num);
}
console.log(typeof square); // Output: number

In this example, the variable square is assigned the value 10. Later, a function with the same name is declared. However, due to the order of precedence, the variable assignment takes precedence over the function declaration. Therefore, when we log the type of square, we get a number.

Example 2: Function declarations over variable declarations

var square;

function square(num) {
return (num*num);
}
console.log(typeof square); // Output: function

In this example, both a variable and a function with the name square are declared. However, due to the order of precedence, the function declaration takes precedence over the variable declaration. Therefore, when we log the type of square, we get the function.

It’s important to keep these rules in mind when writing JavaScript code to avoid unexpected results.

Class hoisting

JavaScript classes can be classified into two classes:

  • Class declarations
  • Class expressions

Class declarations, are similar to their function counterparts and do not undergo hoisting in JavaScript. This means that they remain uninitialized until they are evaluated. Consequently, you have to declare a class before you can use it. For instance, in the code below, a ReferenceError occurs because the class is not defined when car1 is initialized:

var carOne = new car();
carOne.height = 5;
carOne.weight = 500;

console.log(carOne); // Output: ReferenceError: car is not defined

class car {
  constructor(height, weight) {
  this.height = height;
  this.weight = weight;
  }
}

To fix this error, you need to define the car class before initializing car1, and this is done through hoisting in the class declaration:

class car {
  constructor(height, weight) {
  this.height = height;
  this.weight = weight;
  }
}
var carOne = new car();
carOne.height = 5;
carOne.weight = 500;
console.log(carOne); // Output: car {height: 5, weight: 500}

Class expressions, also behave similarly to their function counterparts, and they do not undergo hoisting in JavaScript. This means that if you try to use an undefined class, a TypeError occurs. For instance, the code below results in a TypeError because the shapes class is undefined:

var rectangle = new shapes();
rectangle.height = 10;
rectangle.width = 20;
console.log(rectangle); // Output: TypeError: shapes is not a constructor

var shapes = class {
  constructor(height, width) {
  this.height = height;
  this.width = width;
  }
};

To fix this error, you must define the class before using it, as shown in the corrected code below:

var shapes = class {
  constructor(height, width) {
  this.height = height;
  this.width = width;
  }
};
var rectangle = new shapes();
rectangle.height = 10;
rectangle.width = 20;
console.log(rectangle); // Output: shapes {height: 10, width: 20}

In summary, hoisting is a behaviour in JavaScript that automatically moves variable and function declarations to the top of their respective scopes during compilation. To avoid confusion and errors in your code, it is important to understand the mechanism of hoisting.

Hoisting, however fun it may be, may result in bugs as it is quite easy to overlook.

  1. To avoid bugs, always declare all variables at the beginning of every scope. Since this is how Javascript interprets the code, it is always a good rule.
  2. Use JavaScript strict mode, using the “use strict” directive at the top; JavaScript strict mode does not allow undeclared variables.
  3. Use newer methods of defining. Hoisting does not occur with let or const, using them will increase the chances of your code being bug-free (a lot).

I hope you found it informative and gained a better understanding of hoisting and how it works in JavaScript

Thanks for reading, and happy coding!

Written by Luci
I am a multidisciplinary designer and developer with a main focus on Digital Design and Branding, located in Cluj Napoca, Romania. Profile