We all know JavaScript functions, but do we know what plays under the hood to execute it? In this blog i will talk about the same going step by step to understand how a JavaScript program executes.
Step 1: JavaScript Starts Execution
When a JavaScript program runs, the JavaScript Engine sets up an environment to execute the code. This involves creating execution contexts and managing them on a call stack.
Step 2: Creation of Global Execution Context
The first execution context created is the Global Execution Context (GEC). It has two phases:
Creation Phase:
Memory is allocated for variables, functions, and objects.
Variables are initialized with
undefined
(this is hoisting).Function declarations are stored in memory.
Execution Phase:
The code inside the global scope is executed line by line.
Values are assigned to variables, and function calls are made.
Example:
console.log(a); // undefined (hoisted)
var a = 10;
console.log(a); // 10
Step 3: Function Execution Context
When a function is called, a new Function Execution Context (FEC) is created. Like the GEC, it also has two phases:
Creation Phase:
Space is allocated for the function’s variables and parameters.
The
this
keyword is set based on how the function is called.The outer environment reference is set to enable scope chaining.
Execution Phase:
The function’s code runs line by line.
Any nested function calls create additional execution contexts.
Example:
function greet(name) {
console.log(`Hello, ${name}`);
}
greet('Alice'); // Creates a new FEC and executes the function
Step 4: Call Stack
The Call Stack is a data structure that keeps track of execution contexts.
When a new execution context is created, it is pushed onto the stack.
When the context is completed, it is popped off the stack.
Example:
function first() {
second();
console.log('First function executed');
}
function second() {
console.log('Second function executed');
}
first();
Call Stack:
Global Execution Context
first
(pushed when called)second
(pushed, then popped after execution)first
(popped after execution)
Step 5: Asynchronous Code and Callback Functions
JavaScript is single-threaded, meaning it can only execute one piece of code at a time. Asynchronous operations like setTimeout
or fetch
offload tasks to the browser.
Example:
console.log('Start');
setTimeout(() => console.log('Callback executed'), 2000);
console.log('End');
Execution:
Synchronous Code:
"Start" is logged.
setTimeout
registers the callback and moves it to the Web APIs."End" is logged.
Callback Handling:
- After 2 seconds, the callback function is moved to the Callback Queue.
Step 6: Event Loop
The Event Loop monitors the Call Stack and Callback Queue:
If the Call Stack is empty, the Event Loop picks the first callback from the Callback Queue and pushes it onto the stack for execution.
This ensures asynchronous tasks don’t block the main thread.
Example Execution:
"Start" and "End" are logged immediately.
After 2 seconds, the callback function logs "Callback executed."
Step 7: Microtasks and the Microtask Queue
Promises and async/await
use the Microtask Queue, which has a higher priority than the Callback Queue.
Microtasks are executed before any callbacks in the Callback Queue.
Example:
console.log('Start');
setTimeout(() => console.log('Timeout callback'), 0);
Promise.resolve().then(() => console.log('Promise resolved'));
console.log('End');
Execution Order:
"Start"
"End"
"Promise resolved" (from Microtask Queue)
"Timeout callback" (from Callback Queue)
Summary Flow
Global Execution Context is created and executed.
Functions create new execution contexts, which are pushed to the Call Stack.
Asynchronous tasks are registered and moved to the Callback Queue or Microtask Queue.
The Event Loop manages the execution of queued callbacks once the Call Stack is empty.
This step-by-step flow ensures both synchronous and asynchronous tasks are handled efficiently.