Hoisting
Table of contents
When you're starting with JavaScript you'll surely bump into something known as hoisting
. If you're a beginner, this could be a little tricky as well as confusing because this hoisting
thing lets you access a variable even before declaring it and sometimes lets you invoke a function before its declaration. Yes! you heard that right ๐ถ, some of you might be scratching your head like what exactly do I mean? Don't worry you'll learn almost everything important about hoisting
in this article. So, buckle up your seat belt as we embark on this JavaScript adventure! ๐
What is Hoisting?
Hoisting is a behavior in JavaScript that moves the declaration of functions, variables and classes to the top of their respective scope. This helps us access the variables, functions or classes before declaring them. But there's a catch, only the declarations are hoisted not the initialization. There's a difference between both terms, let's find out.
If you're already familiar with JS's working then you can understand this topic easily and very well because I am going to use terminologies like Global Execution Content
, Call stack
, Temporal dead zone (TDZ)
and more. If you don't know much about how JS works then you can read my blog on this topic. Click here to read ๐.
Understanding Hoisting
When we run our JS code, then during the compilation phase when the AST is created JS keeps in its knowledge about the variables and functions that are defined in the code. The interpreter then moves the declarations to the top of their scope this way JS hoists the declarations.
In hoisting, it seems that the variables, functions, and class declarations are being moved to the top but actually, what happens is that they are being added to the memory during the compilation phase.
To understand things easily just remember one thing for now -> Before the JS code is executed JavaScript Engine skims through the entire code and the memory is allocated to our variables and function declarations, this process is done during parsing. Let's read some code to be more confident.
Example 1:-
console.log(a)
var a = 10;
Output:-
Why the output is undefined? Is it because of hoisting? Let's explore. When the code is being compiled, some memory is allocated to the variable a
and it is added to the Creation phase (Global Memory). Now we can say that a
is hoisted because it has allocated some memory, but why is it undefined? See when we make a variable using the var
keyword then during the compilation phase these variables are initialized with undefined
in the memory but it's not the same with let
and const
. After the JavaScript Engine skims through the entire code, the memory is allocated to the declarations, then the Global Execution Context
goes inside the Call Stack and the code starts executing.
Here, I have my debugger mode ON if you also want to follow along you can open your developer tools and then go to the Sources tab, add the starting point and more then refresh the page.
In the Global
we can see that there is already a variable present in the memory and it is initialized with undefined
. Moving to the next line we'll see that now the variable a
is initialized with the value 10
as we can see in the below screenshot.
Hope the basic concept of hoisting is clear to you. Let's move to some other stuff.
Example 2:-
greet()
function greet() {
console.log("Hello")
}
Output:-
Hmmm... we can access the function before its declaration ๐ต๐maybe that's also because of hoisting right? Yes, that's correct โ
because during the compilation phase when the memory is being allocated to the declarations the function definition goes as it is in the memory, only if it is declared using the function
keyword. For functions, a separate execution context
is created and then this execution context goes inside the call stack and gets executed. Let's check the Global scope
for this case in debugger mode.
In this image, we can see that the function greet
is already present, meaning that in the memory it is stored as a function. This is the reason why when we declare a function using the function
keyword we can access it before we have declared it. Also, we can see that there is something called [[Scopes]]
present inside greet
๐คwe'll explore that later. There is a lot more to explore and if you want to then you can add more debugging points and check the Call Stack
.
(anonymous) is the Global Execution Context
, greet is the Function Execution Context
that is created when the greet
function is invoked.
Example 3:-
console.log(a);
var a = 10; // line 2
aa ();
function aa () {
console.log("a inside function", a);
var a = 20;
}
There's a lot of a
here ๐ I did that on a purpose so that everything can be seen easily in the debugger.
Output:-
Does hoisting work inside a function too? The answer is yes, during the compilation phase memory is allocated to all the declarations, no matter if the declaration is present inside a function or not but it is then limited to that scope (Local Scope). The variable a
that is inside the function will be limited to its local scope only meaning that it will only be accessible inside aa
function, if you comment line number 2 and run the code then you'll get Reference Error.
The first Global
is the global scope and inside that a
is already present and the function aa
is present too, and if you look carefully at the [[Scopes]]
of aa
then you'll see another Global
and this Global
is only limited to the aa
function. In the function's global a
is already present. This concludes that all the declarations are hoisted whether they're inside of a function or not.
Move the debugger to the next line and observe the changes in the Scope and check
Call Stack
too.
Example 4:-
console.log(a)
let a = 10;
Output:-
Why is there a ReferenceError? Does that mean let
declarations are not hoisted? There is a myth about this concept and the myth suggests that let
and const
declarations are not hoisted. Today I'll be debunking this myth.
Similar to var
declarations let
and const
declarations are also hoisted because during compilation they're also allocated some memory but unlike var
they're not initialized with undefined
instead they are initialized with uninitialized
, this might sound confusing but this is how it works ๐.
Where is a
? If a
is hoisted then it must be in the Global scope
right? See whenever we create a variable using let
keyword or a constant using const
keyword then it doesn't go inside the global scope
directly they have to wait until they're initialized. So where is a
currently? They remain in a Temporal Dead Zone (TDZ) until they're initialized with a value, we can think of TDZ as an area where all the let
and const
declarations are inaccessible until they're assigned a value. That's why here also a
is not present in the Global Scope
because it is present in TDZ.
Example 5:-
a()
var a = function () {
console.log("Hello")
}
Output:-
Some of you might be thinking why we got this error? It is a function and it should be hoisted and accessible before its declaration. Yes, this applies to function declaration only and this is not a function declaration it's a function expression. When we have a function stored inside a variable or a constant then it is treated similarly to a normal variable or a constant declaration, meaning that if a function is stored in a var
variable then during the compilation phase when they're allocated memory this variable will be initialized with undefined, and if it was declared using let
or const
then it'll be initialized with uninitialized. That's why the output says that a
is not a function which is quite obvious now because a
is undefined at the current moment.
We can also see in the debugger that a
is undefined. If we change the var
keyword with let
or const
then the error message will change too and this time we'll get ReferenceError : Cannot access 'a' before initialization.
Classes are also hoisted and they have similar behavior like
let
andconst
. When we declare aclass
, it is initialized with uninitialized during the compilation phase and remains in TDZ, and if we try to create an instance using that class before its initialization then we'll get ReferenceError.
โญ Summary
Hoisting is a behavior in which declarations are moved to the top of their respective scopes.
Only declarations are hoisted not the initializations.
The interpreter does not move our declarations to the top of the code, actually the declarations are allocated memory during the compilation phase.
A variable that is created using the
var
keyword is accessible before its declaration because they're initialized with undefined when they're allocated memory.Variable or constant that is declared using
let
andconst
keywords are initialized with uninitialized and they remain in TDZ until they're initialized with a value. That's why we get ReferenceError when we try to access them before their declaration. Same withclasses
.Function declarations are fully hoisted meaning that we can access them before they are defined. But function expression and arrow functions are not they're treated as similar to normal variables or a constant.
Thank you so much for reading my article ๐ฅณ, hope you have learned something new today. If you have any questions or have any feedback for me to improve my article, please feel free to use the comment box. See ya in my next article ๐๐ปโโ๏ธ.
Subscribe to my newsletter
Read articles from Gaurav Goswami directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Gaurav Goswami
Gaurav Goswami
Hello World ๐๐ป๐๐ป I am Gaurav Goswami a MERN stack developer based in India. I like learning about new tech and currently sharpening my skills by learning microservice architecture. Have a good day :)