Learnitweb

Hoisting in JavaScript

1. Introduction

In this tutorial, you’ll learn about hoisting in JavaScript. Hoisting is a very important concept to know for the JavaScript interviews. Hoisting is most commonly asked question in JavaScript interviews. We’ll discuss hoisting with respect to variable, function and classes.

In JavaScript, variables and functions can be accessed before their actual declaration, and the terms used for this in JavaScript is ‘Hoisting’. Hoisting in JavaScript refers to the process in which the JavaScript engine appears to move the declaration of functions, variables or classes to the top of their scope, prior to execution of the code.

In JavaScript, hoisting is possible with all of these:

  • variables
  • functions
  • class declarations

Hoisting allows variables, class declarations and functions to be referenced in code even before they are declared.

Hoisting could result in unexpected errors and that is why not it is not recommended to write code which could result in hoisting.

2. Variable hoisting

Hoisting allows you to use variable before it is declared and/or initialized. We’ll discuss variable hoisting with reference to var and let.

2.1 var hoisting

The term “hoisting” is mostly associated with var variables.

Consider the following code example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(result); // undefined
var result = 2 + 3;
console.log(result); // undefined var result = 2 + 3;
console.log(result); // undefined
var result = 2 + 3;

This code is an example of hoisting. Here, the result is printed without any error before it is declared. This is made possible because of the parsing step before the code is executed.

Before executing the code, the JavaScript engine performs a preliminary scan, allowing it to detect certain errors early on. This scan also manages variable declarations by registering all variables within their respective scopes. Essentially, all variables declared within a scope are reserved for that scope prior to the execution of any code within it. This preprocessing step facilitates hoisting, which enables the use of var variables before they are explicitly declared in the code.

It’s important to note that hoisting of var variables involves only their declarations, not their initial values. These variables are assigned a value of undefined until their actual assignment occurs during the execution phase. For example, in the code provided, the variable will be declared and hoisted with a value of undefined, and only when the execution reaches the assignment statement will it be given the value 5.

There is a misconception about the concept of hoisting that the JavaScript engine moves the hoisted declarations to the top of the file. This makes it easy to understand but this is not correct. JavaScript engines don’t move the hoisted declarations to the top of the file. Instead, the declarations are handled before the code is executed step by step. For var variables, they are initially assigned the value undefined until the declaration is reached. For block-scoped variables, they are marked as “uninitialized” until their declaration is encountered.

Here is another example which runs without error due to hoisting:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log("name is " + name);
var name;
//Output is: name is undefined
console.log("name is " + name); var name; //Output is: name is undefined
console.log("name is " + name);

var name;

//Output is: name is undefined

2.2 let and const hoisting

let and const variables are also hoisted but they can’t be read until initialized. If you try to read such variable, you’ll get ReferenceError.

For example, following code will result in ReferenceError.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(name);
let name = "James";
//ReferenceError: Cannot access 'name' before initialization
console.log(name); let name = "James"; //ReferenceError: Cannot access 'name' before initialization
console.log(name);
let name = "James";

//ReferenceError: Cannot access 'name' before initialization

2.3 Function hoisting

Function hoisting allows you to use a function even before it is declared.

In the case of function declarations, the function’s name is registered as a variable within the scope where the function is declared, and it is initialized with the function itself. This means that the entire function is hoisted to the top of its scope, making it accessible before the point in the code where it is actually declared. Unlike the var variables, there is no initialization with the undefined value in the case of function declarations.

In the following piece of code, we are calling helloWorld() function even before it is declared.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
helloWorld('James');
function helloWorld(name){
console.log("Hello World from " + name);
}
//output is: Hello World from James
helloWorld('James'); function helloWorld(name){ console.log("Hello World from " + name); } //output is: Hello World from James
helloWorld('James');

function helloWorld(name){
    console.log("Hello World from " + name);
}

//output is: Hello World from James

Without hoisting you would have to write code in the following manner:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function helloWorld(name){
console.log("Hello World from " + name);
}
helloWorld('James');
function helloWorld(name){ console.log("Hello World from " + name); } helloWorld('James');
function helloWorld(name){
    console.log("Hello World from " + name);
}

helloWorld('James');

Function declarations inside blocks

In ES2015, the ECMAScript specification defined standard and legacy rules for handling function declarations. Not that the standard rules only come into effect in strict mode.

Standard rules

According to the standard rules, the function declarations inside blocks are hoisted to the top of the block, converted into a function expression, and assigned to a variable declared with the let keyword.

The function hoisted inside the block is limited to the containing block and cannot be accessed by code outside the block containing the function.

Let us understand these statements with an example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
"use strict";
function process() {
if (true) {
console.log("processing...");
function fnInsideBlock() {
console.log("inside a block");
}
}
}
"use strict"; function process() { if (true) { console.log("processing..."); function fnInsideBlock() { console.log("inside a block"); } } }
"use strict";

function process() {
    if (true) {
        console.log("processing...");
    
        function fnInsideBlock() {
            console.log("inside a block");
        }
    }
}

According to the standard rules, the function inside the if block should be treated as shown below:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
"use strict";
function process() {
if (true) {
let fnInsideBlock = function fnInsideBlock() {
console.log("inside a block");
};
console.log("processing...");
}
}
"use strict"; function process() { if (true) { let fnInsideBlock = function fnInsideBlock() { console.log("inside a block"); }; console.log("processing..."); } }
"use strict";

function process() {
	if (true) {
		let fnInsideBlock = function fnInsideBlock() {
			console.log("inside a block");
		};

		console.log("processing...");
	}
}

The fnInsideBlock function in the above code can only be called from within the if block.

Legacy rules

The legacy rules are applied to the non-strict code in web browsers. In the legacy rules the hoisted function inside the block is assigned to a var variable that is declared in the containing function scope.

The legacy rules are complicated and confusing. That is why it is not recommended to write code without strict code.

2.4 class hoisting

Class declarations are hoisted similarly to function declarations, but the way they are hoisted differs from how functions are hoisted.

Let us see an example that the class declarations are hoisted.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let Car = "Maruti";
if (true) {
console.log(typeof Car); // error
class Car {}
}
let Car = "Maruti"; if (true) { console.log(typeof Car); // error class Car {} }
let Car = "Maruti";

if (true) {
	console.log(typeof Car); // error

	class Car {}
}

This code will return following error:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ReferenceError: Cannot access 'Car' before initialization
ReferenceError: Cannot access 'Car' before initialization
ReferenceError: Cannot access 'Car' before initialization

The error thrown by the code confirms that class declarations are hoisted. The fact that the identifier Car inside the if block refers to the class declaration and not the Car variable declared before the if statement proves that the class declarations are indeed hoisted. This is the period between entering the scope and the actual declaration where the variable or class exists but cannot be accessed or initialized.

Classes defined using class declaration are hoisted but must be defined before they can be constructed. The class declaration is done using class keyword.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Person {}
class Person {}
class Person {}

If you try to construct a class which is not defined yet, you’ll get ReferenceError. For example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const p = new Person(); // ReferenceError
class Person {}
const p = new Person(); // ReferenceError class Person {}
const p = new Person(); // ReferenceError

class Person {}

Temporal Dead Zone (TDZ)

Temporal Dead Zone (TDZ) refers to the time during which the block-scoped variables (let, const) or class declarations cannot be accessed. The time starts from the start of the scope till the declaration is executed.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
// TDZ start
console.log("hello");
console.log("world");
let count = 5; // TDZ end
// can access "count" after TDZ ends
}
{ // TDZ start console.log("hello"); console.log("world"); let count = 5; // TDZ end // can access "count" after TDZ ends }
{
	// TDZ start
	console.log("hello");
	console.log("world");
	let count = 5; // TDZ end

	// can access "count" after TDZ ends
}

TDZ also applies to the let and const. So let and const variables are also hoisted, but like the class declarations, they are hoisted differently because of TDZ.

The function and class expression are not hoisted.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(typeof Vehicle); // undefined
console.log(typeof Student); // undefined
var Vehicle = class {
constructor(model) {
this.model = model;
}
};
var Student = function (name) {
this.name = name;
};
console.log(typeof Vehicle); // undefined console.log(typeof Student); // undefined var Vehicle = class { constructor(model) { this.model = model; } }; var Student = function (name) { this.name = name; };
console.log(typeof Vehicle); // undefined
console.log(typeof Student); // undefined

var Vehicle = class {
	constructor(model) {
		this.model = model;
	}
};

var Student = function (name) {
	this.name = name;
};

In this example, undefined is printed because only the declarations are hoisted, not their values. In this example, the values are expressions, and they are not hoisted.

3. JavaScript hoists declarations not initializations

JavaScript hoists only declarations. JavaScript does not hoist initializations. If a variable is used before declaration, then only variable declaration will be hoisted and its value will be initialized only when the initialization statement is executed.

Let us understand this with the help of an example.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log("name is " + name);
var name;
name = 5;
//Output is: name is undefined
console.log("name is " + name); var name; name = 5; //Output is: name is undefined
console.log("name is " + name);

var name;

name = 5;

//Output is: name is undefined

In this code the value of name is printed as undefined. The reason is, name will be initialized only when name=5; is executed. JavaScript will hoist the variable but will not initialize.

Even if we initialize in the same line, the output will be the same.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log("name is " + name);
var name = 5;
//Output is: name is undefined
console.log("name is " + name); var name = 5; //Output is: name is undefined
console.log("name is " + name);

var name = 5;

//Output is: name is undefined

If we only initialize the variable and not declare at all, then the variable will not be hoisted. If you try to read such variable, then you’ll get ReferenceError.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(name);
name = "James";
//ReferenceError: name is not defined
console.log(name); name = "James"; //ReferenceError: name is not defined
console.log(name);
name = "James";

//ReferenceError: name is not defined

However, if you try to read the initialized variable then hoisting will not happen and you’ll not get error. Following piece of code works fine:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
name = "James";
console.log(name);
name = "James"; console.log(name);
name = "James";
console.log(name);

4. Function expressions are not hoisted

Function expressions are not hoisted. Following code will give error:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(getSum(1, 5));
var getSum = function (num1, num2) {
return num1 * num2;
};
console.log(getSum(1, 5)); var getSum = function (num1, num2) { return num1 * num2; };
console.log(getSum(1, 5));

var getSum = function (num1, num2) {
  return num1 * num2;
};

5. class expressions are not hoisted

Similar to function expressions, class expressions are not hoisted. Following code will not work:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(Car.name);
let Car = class {
constructor(model) {
this.model = model;
}
};
console.log(Car.name); let Car = class { constructor(model) { this.model = model; } };
console.log(Car.name);

let Car = class {
  constructor(model) {
    this.model = model;
  }
};

6. Conclusion

In this quick tutorial, we discussed hoisting in JavaScript. It is recommended to learn hoisting by writing code and checking output. You may find questions in interviews about hoisting. This tutorial was a quick one but we hope it was informative.

Happy learning!