Understanding Javascript hoisting
amt8u
Posted on September 28, 2020
Can you guess the output of the below program?
console.log(hoisting);
var hoisting = "Its a variable"
console.log(hoisting);
function hoisting(){
return "Its a function";
}
console.log(hoisting);
var hoisting = "What is it?"
console.log(hoisting);
To know the actual answer you can go to the bottom of the post. If you cannot digest the answer, this post is for you.
To start with, hoisting is defined by the dictionary as Hoisting to raise or lift, especially by some mechanical appliance which basically means to move up.
Hoisting
is JavaScript's default behavior of moving declarations to the top.
w3schools.com
Why do you need to move?
Lets start with simplest example. Fire up your devtools and type in the below line.
// Uncaught ReferenceError: someRandomName is not defined
console.log(someRandomName);
In the above case you will get an error while in the below example you will get undefined
as return value.
// undefined
console.log(someRandomName);
var someRandomName;
For somebody who is starting to learn JS, this is just totally illogical. At first glance you will say "ah.. declarations are given priority". Great. But then if you run the below example, your start losing trust.
// undefined
console.log(someRandomName);
var someRandomName = "Are you trying to find logic here?";
Whats wrong?
Before I explain what hoisting is, you need to unlearn the definition that you have been fed from various sources - Hoisting is not moving the variable declarations to the top. Though once you understand the concept, you would probably agree to the definition. But the problem is when you haven't yet understood, the definition brings more confusion rather than clarity.
Is Javascript interpreted languange?
Its debatable but the simple answer is - its NOT. Javascript is also not a typical compiled language. It lies somewhere in between.
What is it then?
When you supply the script to the js engine, which in most cases would be a browser, it would first parse your script. It will read your script line by line and find out all the declarations made in the scope. Remember it only looks for declarations in the current scope. So by default when it loads the script, it only looks in the global scope.
What all it looks for?
The parser would look for all var
and function
declarations. With ES6, it will also look for const
and let
. But its mostly the same approach except one edge case which we shall cover in a minute. A simple statement like var x=6
has two parts -
- declaration -
var x
- statement -
x=6
Only the declarations are read, assignments are NOT. Assignments are just statements which will be run only in the second stage.
Once all the declarations are identified, the parser keeps a note in memory and asks the engine to start executing the same script line by line. So basically the same code is read twice. Though it will not be technically correct, we can say the first pass is compilation and the second pass is execution of the same code. In traditional compiled languages, compiled version of the original code is executed.
Thats why it doesn't matter where you declare the variable or function. If there is any var
anywhere, it will be registered as declared without a value which by default is represented by undefined
. But if its a function, the function definition also becomes a part of declaration and is stored at the same time.
console.log(x)
var x="Move it";
console.log(x);
Above code can also be represented as below snippet. Many tutorials/articles say that above code gets translated into the below code. That does not happen. This is just an illustration. The parser does not alter/modify your code. Its just how the code is read two times which makes you feel as if the declarations moved from their original location in the code to the top of the function.
var x;
console.log(x);
x = "Move it";
console.log(x);
Got it?
Now that you understand how a script is read and parsed, its time for another confusion
console.log(foo);
function foo(){
return "This is a function.";
}
var foo = "Are you kidding!"
console.log(foo);
As per our understanding till now, the above code should output as below
undefined
Are you kidding!
Instead you will get the below output
Ζ foo(){
return "This is a function.";
}
Are you kidding!
In the first parse, the engine will identify that there is a foo
function declaration, so it assigns an identifier and also associates a function definition to it. Remember function declarations are read differently than assignments as mentioned earlier.
On encountering the second declaration for the same keyword foo
, the engine should override the previously identified function right? But that doesn't happen. Function declarations are always given priority over var declarations. It doesn't matter how many times you write var, after the first parse, only function declaration will be kept.
var bar = 56;
var bar = 99;
function bar(){ return "Function is high priority";}
var bar = "Assign some string";
But what about this case? Multiple function declarations with same name. This time your intuition is right. The last one is taken into account.
foo()
function foo(){console.log("First one");}
foo()
function foo(){console.log("Again!");}
foo()
function foo(){console.log("Not again!");}
foo()
Not again!
Not again!
Not again!
Not again!
Is there any other catch?
let there be. When you ask what is the difference between let and var, a common accepted answer is - let/const
declarations are not hoisted. Consider the below example. If we go by the accepted answer, let
declaration will be ignored in the first parse. In the execution phase, line#1 should create a global variable and assign a string "Gotcha!" to it. And then of course, it would print that value and on line#3 there will be a new local block scoped variable created and assigned a value "Bad karma!".
foo = "Gotcha!";
console.log(foo);
let foo = "Bad karma!";
console.log(foo);
But if you run the code in a fresh window, you will see that it gives an error. A special kind of error.
Uncaught ReferenceError: Cannot access 'foo' before initialization
It turns out that let/const
declarations are also hoisted but assigning values to them is restricted until the line where the declaration is made, gets executed.
The above phoenomenon is called as Temporal Dead Zone. And is only applicable to let/const
block scoped variables.
But why god why?
Now that we know what hoisting is, we shall also dig into - Why its there?
- Whats the purpose of having such a confusing feature?
- Whats the point in executing the script like this?
- Executing line by line would have been much easier?
This is just my perception and I might be wrong here but after reading about JS from multiple sources, I guess it all boils down to the fact that -
Javascript was for content writers. NOT programmers.
When Brendon was asked to come up with a scripting language, the whole idea was to give some control to the HTML creators so that they can create simple actions on the client side. You must have seen code like this on legacy products
<button onClick="showUserData()" name="getdata">Show User Details</button>
function showUserData() {
// update the DOM to show user data on screen.
}
The function showUserData
just toggles some div on the UI. This function can be called from many places, and not just the click event. Some other event can also trigger this function. To make it simple for the creators where they should not worry about defining the variables and functions before using them, Brendon might have come up with this approach.
This method would have made sense at that time when scripts were like 100-200 lines. Only a small set of functions to manage which can be used anywhere in the script as well as html easily.
But slowly JS
started to grow because of the endless possibilities and the rich interactions it provided which HTML
was unable to offer. We started writing scripts with 1000 lines of code and of course imposing practices from other languages which do not fit with the JS design, we have the end result where every thing which was a feature at some point of time is now considered a bad design.
Nevertheless, if you understand the base concept, it becomes easy to program and helps in understanding other's code as well.
Further reading
jsrocks.org - Excellent read for TDZ
Axels' blog - Whys TDZ is there
Output of the code at the top of the article
// Answer to the code sample on the top of the page
Ζ hoisting(){
return "Its a function";
}
Its a variable
Its a variable
What is it?
End
Posted on September 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.