Introduction
Let’s look at the below lines of code,
- var myName = 'Nilanjan';
-
- var printName = () => {
- console.log(myName);
-
- var myName = 'Vishnu';
- }
-
- printName();
What did you think the output would be? Take few seconds to
think and try to answer…
Did you say “Nilanjan” or perhaps “Vishnu” ?
Well, if you are surprised to know (like I was once, honestly !) that the answer is “undefined” you are in the right place!
Coming from a strongly and static typed programming language background, JavaScript never failed to surprise me when I first started learning it. I mean when I started coding with JavaScript apart from simple DOM manipulation (you know what I mean!)
So let’s understand why JavaScript behaves this way. In the JavaScript “literature” this behavior is known as “Hoisting” – remember “Flag Hoisting”? Ya, somewhat relates to that kind of feeling I must say...more like “variable hoisting”.
But before we jump into exploring that, we first must understand another concept in JavaScript, called “Execution Context”.
Execution Context and Its ‘Phases’
JavaScript Engine creates and executes execution context based on the execution order of the code. It means 2 things:
- The JavaScript code can have multiple “Execution Contexts” – not lexically but from the execution order standpoint.
- The engine executes the code in two phases: first it creates the context and then it executes it.
When the Execution Context is created, the variables in it are marked and given their respective memory allocation, without having their values initialized to the intended value. Instead, all of them are assigned with “undefined” values. Function declarations are hoisted too and that's the reason, a function could be called lexically even before it is declared.
At the time of the execution phase, the code is executed line-by-line and the values are then bound to the variables within the lexical scope, i.e. the values within the execution context(s).
Let us now understand this concept with the first code snippet we saw.
- var myName = 'Nilanjan';
-
- var printName = () => {
- console.log(myName);
-
- var myName = 'Vishnu';
- }
-
- printName();
When the JavaScript Engine starts parsing this code, it first creates a Global Execution Context and assigns all the variables within the lexical (read physical) boundary of the global space as “undefined”. Function declarations are hoisted with the entire definition in this phase.
So in the first phase (creation phase), the Global Execution Context looks like this,
Now it starts executing this execution context and assigns value to the variables and when it finds those initialization - line-by-line.
While doing so “myName” variable receives its value as “Nilanjan” in Line 1 and printName function gets a function assigned to it in the next line.
In the end, the JavaScript engine receives a function invocation instruction. This is an important phase of execution, because here, the engine now creates another fresh execution context for the function to be executed.
Observe, the code inside this printName function.
- var printName = () => {
- console.log(myName);
-
- var myName = 'Vishnu';
- }
Now, the function’s new execution context now goes through the same 2 phases: 1. Creation, 2. Execution.
In the creation phase, as the engine scans through the code within the function, execution context has only one variable to mark as “undefined”, i.e. myName.
In the execution phase, when the function is executed line-by-line, it finds in the first line instruction to print the value of myName variable before the actual value initialization.
So what does it do? It spits out the value it had assigned during the “creation” phase, i.e. undefined.
In the below visualization, I have captured the binding of the values in the variables in each phase of execution,
Since the console.log happens before the real initialization of value to the myName variable inside the printName function, it prints the value “undefined” which had been assigned to it during the creation phase of the execution.
Bonus
Well, we all love bonuses, don’t we !
So what would happen if we had marked the variables in our that piece of code “let” or “const”?
The JavaScript engine still “hoists” them, however in the creation phase of the execution context, instead of assigning them “undefined”, they remain uninitialized.
Hence, if we run this code changing the var into let/const like this:
- let myName = 'Nilanjan';
-
- const printName = () => {
- console.log(myName);
-
- let myName = 'Vishnu';
- }
-
- printName();
We would get this error: - console.log(myName);
- ^
- ReferenceError: Cannot access 'myName' before initialization
So next time you see a variable being accessed before it is actually being “initialized” with real values, you know how JavaScript will behave.
Summary
Many times, it is described as if the variables or functions (only declaration not expressions) are moved to the top of the code but now you know it's perhaps not the right explanation. It all boils down to how the two different phases of the execution within the JavaScript engine initializes the variables and function declarations.
Before I leave you with this, a parting note, function expressions are NOT hoisted. In this example, the printName is a function expression. This is NOT hoisted unlike the usual function declaration. You can see what would happen below with such code:
- var myName = 'Nilanjan';
- printName();
-
- console.log(printName);
-
- var printName = () => {
- console.log(myName);
-
- var myName = 'Vishnu';
- }
-
- printName();
Happy coding!!