Hoisting is a JavaScript behavior, where JavaScript creates the variable and functions before executing the script. This allows us to use the function or variable even before we declare them. In this tutorial, let us learn what hoisting is and how it works in JavaScript.
Table of Contents
What is Hoisting
Most sites define Hoisting as JavaScript’s behavior where it moves all declarations to the top of the current scope. Although It is what appears to be happening it is not true.
But first, let us understand what is hoisting using an example.
Take a look at the following example, where we try to access a variable before its declaration.
1 2 3 4 | console.log(a) //undefined var a=1; |
Since JavaScript is an Interpreted language, we expect it to read each line of code and execute it before moving on to the next line. Hence it is natural to assume that the console.log
statement will throw an error. Because when JavaScript executes the console.log
statement, the variable a
does not exist yet.
However, the console.log
output will be undefined.
The undefined
value indicates JavaScript already created the variable and also assigned undefined value it. It has created it even though it is yet to execute the line where we have actually created it.
Here it appears that JavaScript has moved the declaration of the variable to the top of the current scope as shown in the code below.
1 2 3 4 5 | var a console.log(a) //undefined a=1; |
The same goes for functions. The following code work without any error, although we execute the sayHi
function even before we declare it.
1 2 3 4 5 6 7 8 | sayHi(); //Hi function sayHi() { console.log('Hi'); } |
Here also it appears that JavaScript moves the sayHi
function to the top of the current scope before execution as shown below.
1 2 3 4 5 6 7 | function sayHi() { console.log('Hi'); } sayHi(); //Hi |
This behavior of JavaScript where it appears to move all declarations to the top of the current scope is Hoisting.
How does it work
JavaScript is an interpreted language. The JavaScript engine is responsible for interpreting each line of code and executing it.
But JavaScript also compiles the code before executing it. In fact, it executes the code in two phases
Compilation Phase: (or Preparation Phase) where JavaScript runs through the code, Parses it, creates execution context, and does a lot of other stuff.
Execution Phase: where the JavaScript interpreter actually runs the code line-by-line.
As part of the compilation phase, JavaScript looks for all declarations and creates the variables and functions in memory. Hoisting is a side effect of such behavior
In the example below, the JavaScript compiler reads the line var a=1
and creates the variable a
in the memory. It assigns the value undefined
to the created variable.
1 2 3 4 | console.log(a) //undefined var a=1; |
Note that the statement var a=1
actually consists of two statements. One is declaration i.e. var a
and the other one is an assignment a=1
. The JavaScript compiler only processes the declaration and not the assignment.
Once the Compiler completes its work, the JavaScript engine begins executing the code one line at a time. It first executes the code console.log(a)
. Since the variable a is already created and initialized with undefined
, it prints undefined
in the console window.
JavaScript also hoists the variable declared with let
& const
. Modify the above example and replace the var
with let
. Now instead of undefined
we get Cannot access [variable name] before initialization
.
1 2 3 4 | console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization let a=1; |
That is because, in the case of let
(also const
), the JavaScript Hoists the variable but does not initialize it with any value.
Variable Life Cycle
In JavaScript variables also go through three stages during the compilation & execution.
Declaration: phase is where JavaScript registers the variable in the scope.
Initialization: phase is where the engine allocates memory to the variable and assigns an initial value of undefined
Assignment: is where JavaScript assigns value to the variable.
Whether these phases happen at compile-time or at execution time depends on how we declared the variable. The following table shows the relationship between the compilation & execution phase with the let
, var
, const
& function
declarations
Keyword | Declaration | Initialization | Assignment |
---|---|---|---|
var | Compilation | Compilation | Execution |
let & const | Compilation | Execution | Execution |
function | Compilation | Compilation | Compilation |
The summary of the above table is
- For a
var
variable declaration and Initialization are hoisted. But not the assignment. - For a
let
&const
variable declaration is hoisted. But not initialization and assignment. - For a
function
declaration, initialization, and assignment are hoisted.
Error Messages
The error messages that JavaScript throws when we try to access a variable give us a hint about the variable’s current state.
Before Declaration
JavaScript throws a "[variable name] is not defined"
error whenever we try to access an undeclared variable. In the following example, we have not declared the variable a
hence it throws the error "a is not defined"
.
1 2 3 | console.log(a) //Uncaught ReferenceError: a is not defined |
Before Initialization
The JavaScript hoists the variables declared with let
or const
but does not initialize them. When we try to access a variable that is not initialized, we get Cannot access [variable name] before initialization
error.
1 2 3 4 | console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization let a=1; |
After Initialization
JavaScript assigns undefined
to every variable which we have declared using var
but not yet initialized. In the following code, we have declared the variable but have not assigned any value to it. Hence the code outputs the undefined
.
1 2 3 4 | var a; console.log(a) //undefined |
Remember that we can also assign undefined
to a variable.
Hence for a variable
- that we have not declared the JavaScript throws
[variable name] is not defined
error. - declared but not initialized, then we get the error
Cannot access [variable name] before initialization
- declared & Initialized but not yet assigned any value, then we get
undefined
. JavaScript does not throw any errors here.
Variable Hoisting
We already looked at the following example.
The javascript does the following in the compilation phase
- variable
a
is registered with the scope - created in memory with the initial value of
undefined
Then the execution begins
1 2 3 4 | console.log(a) //undefined var a=1; |
The compilation phase when using let
.
- variable
a
is registered with the scope
Then the execution begins
1 2 3 4 | console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization let a=1; |
During the compilation phase, the engine registers the variable with the scope. During the execution time, it assigns undefined
to a
when it encounters the declaration statement.
1 2 3 4 5 | let a //a is assigned undefined here during execution console.log(a) //Undefined a=1; |
Redeclaring the Variable
The code declares the variable a
after initializing it with the value 2
. Here the declaration var a
is hoisted at the compile time. Hence the code does not throw any errors.
1 2 3 4 5 | a = 2; var a; console.log(a); //2 |
But if we use let
instead of var
, you will get the Cannot access 'a' before initialization
error. This is because the statement let a
is hoisted but is not initialized. Hence when we access it on the first line, we get the error.
1 2 3 4 5 | a = 2; //Uncaught ReferenceError: Cannot access 'a' before initialization let a; console.log(a); |
Undeclared variables
The following code throws the a is not defined
error. This is because hoisting works only for the declaration made using the var
, let
& const
. The statement a=2
does not declare the variable hence not hoisted.
1 2 3 4 | console.log(a); //Uncaught ReferenceError: a is not defined a = 2; |
The following code works fine because the statement a=2
creates the global variable at the time of the execution phase.
1 2 3 4 | a = 2; console.log(a); //2 |
In the strict mode, you can not use undeclared variables. Hence the above code throws an error.
1 2 3 4 5 6 | "use strict"; a = 2; //a is not defined console.log(a); |
Nested Scopes
The variables declared inside the scope are not accessible outside of their scope.
The let
variables are block-scoped. JavaScript Engine hoists them to the top of the scope in which we have declared them.
The following example let a=3
statement creates a variable a
during the compile time, but within the scope of the code block. Since it is declared with let
it is not initialized with undefined
. Hence we get the error.
1 2 3 4 5 6 7 | var a=2; { console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization let a=3 } |
Note that we already have a variable a
declared in the global scope. But console.log
will not use it as the a
declared in the code block overrides it.
If you remove the let
declaration, then the console.log
will use the a
from the global scope.
1 2 3 4 5 6 | var a=2; { console.log(a) //2 } |
But var
variable ignore the block scopes. Hence the following code works without error.
1 2 3 4 5 6 7 8 9 | var a=2; { console.log(a) //2 var a=3 console.log(a) //3 } console.log(a) //3 |
The Functions also create nested scopes. Hence the variable a
inside the function is local to the function and hoisted within the function.
1 2 3 4 5 6 7 8 9 10 11 12 | var a=2; sayHi= function() { console.log(a) //undefined var a=3 console.log(a) //3 } sayHi() console.log(a) //2 |
Here variable a
is hoisted by JavaScript, but the assignment never happens because the If condition evaluates to false.
1 2 3 4 5 6 7 | console.log(a) //undifend if (false) { var a =10 } console.log(a) //undifend |
Function Hoisting
JavaScript hoists the Function declarations. In the following example, the compilation phase creates the function variable sayHi, initializes it, and assigns it with the function.
1 2 3 4 5 6 7 | sayHi(); //Hi function sayHi() { console.log('Hi'); } |
Function assignment
JavaScript engine only hoists the Function declarations and not assignments. In the following example, we assign the function to a sayHi
variable using var
. Since the sayHi
uses var
for its declaration, JavaScript creates it with the initial value of undefined
.
1 2 3 4 5 6 | sayHi() //Uncaught TypeError: sayHi is not a function var sayHi = function () { console.log('Hi'); } |
You can verify that by checking the value of sayHi
1 2 3 4 5 6 | console.log(sayHi) //undefined var sayHi = function () { console.log('Hi'); } |
Naturally, a let
declaration throws an error.
1 2 3 4 5 6 7 | console.log(sayHi) //Cannot access 'sayHi' before initialization let sayHi = function () { console.log('Hi'); } |
Function Declaration vs Var Declaration
When a function and a variable have the same name, JavaScript ignores the variable declaration at the compiling step. The function always has a higher priority.
In the following example, sayHi
variable points to the function even though the next line of code declares a variable of the same name.
1 2 3 4 5 6 7 8 9 10 | sayHi() //hi function sayHi () { console.log('Hi'); } var sayHi="0" |
Note that let
does not allow us the redeclare a variable. The above code with let
instead of var
will throw the error.
Temporal Dead Zone (TDZ)
The Term Temporal Dead Zone refers to the area in the code where the variable that we are trying to access is declared but not yet initialized. This only happens with let
& const
declarations because the var
declarations are always initialized with undefined
.
In the code below, the area code from the start of the function until the let
declaration is Temporal Dead Zone for variable a
. Because of Hoisting, the JavaScript engine has created the variable before the execution of the function and has yet to initialize it.
1 2 3 4 5 6 7 8 9 10 11 12 | sayHi(); function sayHi() { //TDZ starts here for variable a console.log('Hi'); //TDZ ends here let a=10; } |
Similarly, a variable in the global scope TDZ starts from the top.
1 2 3 4 5 6 7 8 9 10 11 12 | //TDZ for variable a sayHi(); //TDZ till here let a=10; function sayHi() { console.log('Hi'); } |
Avoid Hoisting
Hoisting makes it difficult to detect bugs in JavaScript. It makes the code confusing and difficult to read. Hence it is better to avoid it wherever possible. Here are some tips to avoid Hoisting.
- Avoid
var
. Always uselet
&const
- Always declare the variable at the top of the scope
- Make use of
"use strict"
- Use function expressions instead of function declarations