Hoisting

Gaurav GoswamiGaurav Goswami
8 min read

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 and const. When we declare a class, 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 and const 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 with classes.

  • 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 ๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ.

0
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 :)