How does a JavaScript engine work? About Heap, stack, execution context, and call stack in JavaScript in-depth

How does a JavaScript engine work? About Heap, stack, execution context, and call stack in JavaScript in-depth

·

5 min read

What is the difference between Stack and Heap in JavaScript?

In JavaScript, memory is divided into two main areas: the stack and the heap. These two areas are used for different purposes and have distinct characteristics.

Stack

The stack is a region of memory that operates in a last-in, first-out (LIFO) fashion. It is used for keeping track of function calls, local variables, and the execution context.

Each time a function is called, a new stack frame is created, and it contains information about the function's local variables, parameters, and return address.

When a function completes its execution, its stack frame is popped off the stack, and control returns to the previous function.

The stack is typically limited in size, and if it runs out of space, it leads to a stack overflow.

In short, all the primitive datatypes are used in stack memory, and when memory is defined in a stack or used in the stack, it means whatever variable you declare gets its copy, not the original value.

For example:

// Variables are stored in stack
let myBlog = 'JavaScript Blog';
let otherBlog = myBlog;
otherBlog = 'Js In depth blogs'; // Trying to change in origional value
console.log(myBlog);
Output: JavaScript Blog
console.log(otherBlog);
Output: Js In depth blogs

In this example, when you change the assigned variable to change the original value, the original value doesn't change because it provides a copy of that variable.

Heap

The heap is a region of memory used for dynamically allocated memory. It is used for storing objects and data structures that have a longer lifetime than a single function call.

Unlike the stack, memory allocation and deallocation in the heap are manual and need to be managed by the developer (or the JavaScript runtime in the case of garbage collection).

Variables stored in the heap can be accessed from anywhere in the program, and their lifespan is not tied to a specific function call.

In short, all the non-primitive datatypes are used in heap memory, and when memory is defined in a heap or used in the heap, it means whatever object or anything you declare, you get the reference to the original value.

For example:

// Object are stored in the heap
let userOne = {
  email: 'user@hotmail.com',
  id: 'user1234',
};
let userTwo = userOne;
userTwo.email = 'newuser@gmail.com';
console.log(userOne);
Output: { email: 'newuser@gmail.com', id: 'user1234' }
console.log(userTwo);
Output: { email: 'newuser@gmail.com', id: 'user1234' }

In this example, when you change the assigned object to change the original value, the original value is changed because it provides a reference to the original value.

Garbage collection in JavaScript

In languages like C, C++, etc., it’s our job to allocate memory and safely deallocate it after use. Therefore, if the memory is not freed up after use, the program will run with an unused allocated memory block. We term this condition a memory leak. Therefore, to prevent such conditions, modern programming languages, like JavaScript, come with garbage collectors.

A garbage collector is a part of the JavaScript engine whose job is to free up unused memory from the heap using a garbage collection process. It ensures proper memory management while the application is running. Therefore, the programmer doesn’t have to worry about manual memory management; the garbage collector does it.

Now let's talk about the execution context and call stack in JavaScript.

In JavaScript, the execution context is a concept that plays a crucial role in understanding how code is executed. It represents the environment in which JavaScript code is executed, including variables, functions, and the scope chain. There are two types of execution contexts in JavaScript: the global execution context, which is referred to as the thiskeyword, and the function or functional execution context.

1. Global Execution Context

The global execution context is the outermost context, representing the entire script or program. It is created when the script is run, and it includes two main components: the global object and the thiskeyword. In a browser environment, the global object is the windowobject. In Node.js, it is the globalobject.

2. Function Execution Context

In the context of JavaScript and its execution model, the term "Function Execution Context" typically refers to the environment in which a function is executed. The Function Execution Context involves the memory creation phase or creation phase, and the execution phase. The difference in both is that in the memory creation phase, your variable or anything that you declare has allocated the memory or space. But in the executed phase, everything that you allocated in the memory creation phase has to be executed.

Let's understand it with the help of an example of JavaScript code.

let value1 = 10;
let value2 = 5;
function addNumbers(num1, num2) {
  let total = num1 + num2;
  return total;
}
let result1 = addNumbers(value1, value2);
let result2 = addNumbers(5,7);

Let's see how this code will be executed in JavaScript.

Step 1: Global Execution Context that refers to the thiskeyword.

Step 2: Memory Creation Phase ---> To keep all the variables

value1 ----------> undefined

value2 ----------> undefined

addNumbers ----------> definition ( everything in the function ) because there is execution, only declaration

result1 ----------> undefined

result2 ----------> undefined

Step 3: Execution Phase

value1 ----------> 10

value2 ----------> 5

addNumbers ----------> Here, nothing happens because there is nothing to execute

result1 ----------> It takes addNumbers. But addNumbers is a function. Now what happens? For this function, one more Executional Context is ready.

( That has New Variable Environment + Execution Thread. Now for addnumbers Executional Context occurs in two phases

Step 1: Memory Creation Phase

value1 ----------> undefined

value2 ----------> undefined

total ----------> undefined

Step 2: Execution Phase

value1 ----------> 10

value2 ----------> 5

total ----------> 15 Now this total is returned to the Global Execution Context

After processing everything, this execution context is deleted.

)

Now go back to Step 3: Execution Phase.

result1 ----------> 15

result2 ----------> It also takes addNumbers which is a function. Now what happens? For this function, one more Executional Context is ready again.

( That has New Variable Environment + Execution Thread. Now for addnumbers Executional Context occurs in two phases again

Step 1: Memory Creation Phase

value1 ----------> undefined

value2 ----------> undefined

total ----------> undefined

Step 2: Execution Phase

value1 ----------> 5

value2 ----------> 7

total ----------> 12 Now this total is returned to the Global Execution Context

After processing everything, this execution context is deleted.

)

Now go back to Step 3: Execution Phase.

result2 ----------> 12

Everything you see occurs in the Call Stack.

Execution context was put inside the stack and moved out.

Â