Whether you’ve just started learning JavaScript or have some experience, you might have, at least once, searched for how JavaScript works behind the scenes. If so, you might have come across terms like “execution context” and “hoisting,” two topics that are closely related to each other.
Today, you’ll grasp both of them through the most straightforward explanations you’ll ever encounter.
Introduction
JavaScript is an interpreted, single-threaded language that processes one command at a time. To understand how JavaScript scripts are executed, it’s essential to comprehend the concept of execution context and hoisting.
Execution Context
The execution context is the environment created when running JavaScript code. There are two types of execution contexts: Global execution context and function execution context. The global execution context represents the global scope, it’s created when JavaScript first runs. The function execution context is created whenever a function is called.
Execution Context Phases
1. Creation Phase
During this phase, the global object is created, the this keyword is generated and linked to the global object, and functions and variables are allocated memory and stored (variables are set to undefined).
2. Execution Phase
In this phase, the code is executed line by line, and a new execution context is created whenever a function is called.
Let’s understand it with an example.
var x = 3;
function getSquare(n) {
var square = n ** 2;
return square;
}
var theSquare = getSquare(x);
console.log(theSquare);
Creation Phase:
Line 1: Allocates memory for the variable x
and initializes it with the value undefined
.
Line 2: Allocates memory for the function getSquare()
and stores the entire code within it.
Line 6: Allocates memory for the variable theSqureand
and initializes it with undefined
.
Execution Phase:
Line 1: Sets the variable x
to the value 3.
Line 2: Skips the function getSquare()
as there is nothing to execute.
Line 6: Initiates a new execution context (function execution context) as the function is called.
Creation Phase (function execution context):
-
Line 2: Allocates memory for the variable
n
and initializes it withundefined
. Line 3: Allocates memory for the variable
square
and initializes
it withundefined
.
Execution Phase (function execution context):Line 2: Assigns 3 to the variable
n
.Line 3: Calculates and assigns the result to the variable
square
.Line 4: Returns to the global execution context.
Line 6: Assigns the returned value of square
into the variable theSqure
.
Now if we console log the output(line8) we will get the following:
What if we console log the output before all the code?
console.log(theSquare);
var x = 3;
function getSquare(n) {
var square = n ** 2;
return square;
}
var theSquare = getSquare(x);
You might wonder why the default value for variables is set to undefined. This is a result of the creation phase during the execution context, as discussed earlier. In this phase, variables, including theSqure
, are allocated memory with an initial value of undefined
.
Similarly, if you attempt to console log the variable x
before its initialization, you will also get undefined
.
console.log(x);
var x = 3;
Understanding the execution context and its creation phase simplifies grasping the concept of hoisting.
Hoisting
Hoisting is frequently described as the interpreter moving variable and function declarations to the top of their scope before code execution. However, now that you understand the creation phase of the execution context, you realize that this explanation is a simplification to help conceptualize the behavior.
Example1
Variable hoisting
console.log(x);
var x = 2;
console.log(x);
In this example, even though we console log before the variable x
is declared and assigned a value, it doesn’t result in an error. Because of hoisting the variable x
in the first console log outputs undefined
(Allocates memory for the variable x and initializes it with the value undefined).
Example2
Function hoisting
console.log(getCube(2));
function getCube(x) {
return x ** 3;
}
console.log(getCube(2));
Here, the function getCube()
is called before its declaration in the code. Because of hoisting the entire function gets stored(Allocates memory for the function getCube() and stores the entire code within it.), allowing it to be invoked before its actual placement in the code.
It’s crucial to distinguish between function declaration and function expression.
console.log(getCube(2));
var getCube = (x) => {
return x ** 3;
};
console.log(getCube(2));
In the code, getCube()
is invoked before it is declared. Due to hoisting, the variable declaration(var getCube
)is hoisted, but the function definition ((x) => { return x ** 3; }
)is not. So, when the first console log is encountered, getCube()
is still undefined
at that point, and trying to invoke it as a function will result in a TypeError
.
At this juncture, you may notice that we opted for using var
for variable declarations instead of let
or const
. Why this decision? Simply put, it's to enhance comprehension of the example. While let
and const
are block-scoped and also hoisted, variables declared with them are not accessible before initialization—unlike variables declared with var
, which are hoisted and stored in memory as undefined during the creation phase of the execution context.
The rationale behind the inaccessibility of variables declared with let
and const
before initialization lies in what is known as the Temporal Dead Zone (TDZ).
Temporal dead zone (TDZ) is the area of a block where a variable is inaccessible until the moment the computer completely initializes it with a value
A simple diagram to illustrate the difference between the var and let lifecycles
Conclusion
Whether you’re just starting out with JavaScript or have considerable experience, a thorough understanding of execution context and hoisting is paramount. Delving into the mechanics of how JavaScript functions behind the scenes not only enhances your workflow but also becomes a valuable time-saving skill.
Top comments (0)