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:
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:
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
.
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.
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:
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:
"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:
"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.
let Car = "Maruti"; if (true) { console.log(typeof Car); // error class Car {} }
This code will return following error:
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.
class Person {}
If you try to construct a class which is not defined yet, you’ll get ReferenceError
. For example:
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.
{ // 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.
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.
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.
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
.
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:
name = "James"; console.log(name);
4. Function expressions are not hoisted
Function expressions are not hoisted. Following code will give error:
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:
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!