DEV Community

Cover image for JavaScript Execution Context & Hoisting
Badreddine Boudaoud
Badreddine Boudaoud

Posted on

JavaScript Execution Context & Hoisting

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);
Enter fullscreen mode Exit fullscreen mode

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 xto 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 nand initializes it with undefined.
  • Line 3: Allocates memory for the variable square and initializes
    it with undefined.
    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 squareinto the variable theSqure.

Now if we console log the output(line8) we will get the following:

code example

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);
Enter fullscreen mode Exit fullscreen mode

The output is undefined
code example

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;
Enter fullscreen mode Exit fullscreen mode

code example

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);
Enter fullscreen mode Exit fullscreen mode

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).
code example

Example2

Function hoisting

console.log(getCube(2));

function getCube(x) {
   return x ** 3;
}
console.log(getCube(2));
Enter fullscreen mode Exit fullscreen mode

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.
code example

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));
Enter fullscreen mode Exit fullscreen mode

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 undefinedat that point, and trying to invoke it as a function will result in a TypeError.
code example

At this juncture, you may notice that we opted for using varfor variable declarations instead of let or const. Why this decision? Simply put, it's to enhance comprehension of the example. While let and constare 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 diagram to illustrate the difference between the var and let lifecycles
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)