JavaScript Execution Context - How does JS work on behind?
I am a Python Developer. I am currently learning DevOps. I have previously built some websites using HTML, CSS and JavaScript.
Welcome back to my blogs. Let's delve deeper into the JavaScript execution context with more detailed explanations and examples.
JavaScript execution context is a fundamental concept that plays a crucial role in how JavaScript code is executed. It involves the creation and management of environments where code runs. This article will explore the different types of execution contexts, their characteristics, and how they contribute to the overall behaviour of JavaScript programs.
Global Execution Context
The global execution context is the default context in which JavaScript code runs. It represents the entire script and contains globally declared variables and functions. Variables declared with let, const, or var outside of any function or block scope become properties of the global object (window in browsers).
let globalVar = 'I am global'; // Global variable
function globalFunction() {
console.log('Global function called');
}
console.log(globalVar); // Output: 'I am global'
globalFunction(); // Output: 'Global function called'
In the example above, globalVar and globalFunction are part of the global execution context. The console.log statements access and display the global variable and call the global function.
Function Execution Context
Function execution contexts are created when functions are invoked. Each function call generates its execution context, encapsulating local variables, parameters, and a reference to the outer (enclosing) execution context, forming what is known as the scope chain.
function outerFunction(outerParam) {
let outerVar = 'I am outer';
function innerFunction(innerParam) {
let innerVar = 'I am inner';
console.log(outerParam); // Accessing parameter from the outer function
console.log(outerVar); // Accessing variable from the outer function
console.log(innerParam); // Accessing parameter from the inner function
console.log(innerVar); // Accessing local variable
}
innerFunction('Inner Param');
}
outerFunction('Outer Param');
In this example, outerFunction is called with the parameter 'Outer Param'. Inside outerFunction, innerFunction is called with the parameter 'Inner Param'. The inner function has access to both its local variables and the variables/parameters of the outer function due to the scope chain.
Scope and Scope Chain
Scope refers to the region of code where a particular variable can be accessed or modified. JavaScript uses lexical scoping, which means the scope is determined by the placement of variables and functions in the source code.
The scope chain is a hierarchical structure that defines the order in which JavaScript looks for variables. When a variable is accessed, JavaScript first looks in the current scope. If the variable is not found, it looks in the next outer scope, continuing up the chain until the global scope.
function outerScope() {
let outerVar = 'I am in the outer scope';
function innerScope() {
let innerVar = 'I am in the inner scope';
console.log(outerVar); // Accessing variable from the outer scope
}
innerScope();
}
outerScope();
In this example, innerScope has access to outerVar because of the scope chain. When console.log(outerVar) is executed inside innerScope, JavaScript looks in the inner scope first, doesn't find outerVar, then looks in the outer scope where it finds and logs the variable.
Closures
Closures are a powerful concept in JavaScript that arises from the combination of functions and the scope chain. A closure occurs when a function is defined inside another function, and the inner function has access to the outer function's variables, even after the outer function has finished executing.
function outerClosure() {
let outerVar = 'I am from the closure';
function innerClosure() {
console.log(outerVar); // Accessing variable from the outer function
}
return innerClosure;
}
const closureFunction = outerClosure();
closureFunction(); // Output: 'I am from the closure'
In this example, outerClosure returns innerClosure, forming a closure. When closureFunction is invoked, it still has access to the outerVar variable from the outer function, even though outerClosure has completed its execution.
The Execution Stack (Call Stack)
The execution stack, often referred to as the call stack, is a mechanism that manages the order of execution contexts in JavaScript. When a function is called, a new execution context is pushed onto the stack. When the function completes, its context is popped off the stack.
The call stack is a crucial component in understanding the flow of code execution. It is a data structure that follows the Last In, First Out (LIFO) principle, meaning the last function called is the first to be executed. The call stack keeps track of the currently executing context.
How the Call Stack Works
Function Invocation:
When a function is called, a new execution context is created.
The context is pushed onto the call stack.
Execution:
- The JavaScript engine executes the statements within the current context.
Return:
- Once the function completes execution, its context is popped from the call stack.
Recursion and the Call Stack
Recursion, a technique where a function calls itself, demonstrates the recursive nature of the call stack. Each recursive call adds a new context to the stack until the base case is reached, at which point the stack starts unwinding.
Example: Understanding Execution Context and Call Stack
Let's explore a simple example to illustrate these concepts:
function greet(name) {
let message = `Hello, ${name}!`;
console.log(message);
}
function welcome() {
let greeting = 'Welcome to JavaScript!';
greet('User');
console.log(greeting);
}
welcome();
Global Execution Context:
Created initially.
Variables like
greetandwelcomeare added to the global scope.
Execution of
welcomeFunction:welcomefunction is called, creating a new execution context.greetingvariable is added to thewelcomecontext.greetfunction is called, creating another execution context.messagevariable is added to thegreetcontext.Log statements execute in the reverse order.
Call Stack Visualization:
| greet() | <- greet context | welcome() | <- welcome context | (global) | <- global context- The call stack is visualized with the current execution context at the top.
Call Stack Unwinding:
- As functions complete execution, their contexts are popped from the stack.
| (global) | <- global context- The call stack is empty once all functions complete execution.
Asynchronous JavaScript and the Event Loop
JavaScript is often involved in asynchronous operations, such as handling user input or making API requests. To manage these scenarios, JavaScript employs the event loop and callback queue.
Event Loop
The event loop is a continuous process that checks the call stack and the callback queue. If the call stack is empty, the event loop dequeues functions from the callback queue and adds them to the call stack for execution.
Callback Queue
When an asynchronous operation completes, a callback function is pushed into the callback queue. The event loop ensures that these callbacks are executed in the correct order.
Debugging Execution Context and Call Stack
Understanding how to debug issues related to execution context and the call stack is essential for efficient JavaScript development. Modern browsers provide robust developer tools that enable developers to inspect and debug code execution.
Browser Developer Tools:
Utilize the "Sources" panel to set breakpoints, inspect variables, and step through code.
The call stack and current execution context are visible in the "Call Stack" and "Scope" panels.
Console Logging:
- Insert
console.logstatements strategically to log variable values and track the flow of execution.
- Insert
Debugger Statement:
- Place the
debuggerstatement in the code to pause execution and open the debugger when reached.
- Place the
function example() {
debugger;
// Code to be inspected
}
The this Keyword
The this keyword refers to the object to which a function belongs or the object that is the current execution context. Its value is determined by how a function is called, and it can behave differently in various situations.
const myObject = {
property: 'I am a property',
method: function() {
console.log(this.property); // Accessing property using 'this'
}
};
myObject.method(); // Output: 'I am a property'
In this example, this within the method function refers to the myObject because it is the object that the function is a method of. Understanding the context in which this is used is crucial for effective object-oriented programming in JavaScript.
Dynamic Nature of Execution Context
JavaScript is a dynamically typed and loosely-typed language, meaning that variables can change their types, and the type of a variable is determined at runtime. This dynamic nature extends to execution contexts as well.
function dynamicContextExample() {
if (true) {
var dynamicVar = 'I am dynamically scoped';
}
console.log(dynamicVar); // Output: 'I am dynamically scoped'
}
dynamicContextExample();
In this example, dynamicVar is declared using var inside a block. However, due to the lack of block-scoping with var, dynamicVar is accessible outside the block. Understanding the dynamic nature of execution contexts helps in avoiding unexpected behaviour in your code.
Call Stack
The call stack is a crucial component in understanding the flow of code execution. It is a data structure that follows the Last In, First Out (LIFO) principle, meaning the last function called is the first to be executed. The call stack keeps track of the currently executing context.
How the Call Stack Works
Function Invocation:
When a function is called, a new execution context is created.
The context is pushed onto the call stack.
Execution:
- The JavaScript engine executes the statements within the current context.
Return:
- Once the function completes execution, its context is popped from the call stack.
Recursion and the Call Stack
Recursion, a technique where a function calls itself, demonstrates the recursive nature of the call stack. Each recursive call adds a new context to the stack until the base case is reached, at which point the stack starts unwinding.
Example: Understanding Execution Context and Call Stack
Let's explore a simple example to illustrate these concepts:
function greet(name) {
let message = `Hello, ${name}!`;
console.log(message);
}
function welcome() {
let greeting = 'Welcome to JavaScript!';
greet('User');
console.log(greeting);
}
welcome();
Global Execution Context:
Created initially.
Variables like
greetandwelcomeare added to the global scope.
Execution of
welcomeFunction:welcomefunction is called, creating a new execution context.greetingvariable is added to thewelcomecontext.greetfunction is called, creating another execution context.messagevariable is added to thegreetcontext.Log statements execute in the reverse order.
Call Stack Visualization:
| greet() | <- greet context | welcome() | <- welcome context | (global) | <- global context- The call stack is visualized with the current execution context at the top.
Call Stack Unwinding:
- As functions complete execution, their contexts are popped from the stack.
| (global) | <- global context- The call stack is empty once all functions complete execution.
Asynchronous JavaScript and the Event Loop
JavaScript is often involved in asynchronous operations, such as handling user input or making API requests. To manage these scenarios, JavaScript employs the event loop and callback queue.
Event Loop
The event loop is a continuous process that checks the call stack and the callback queue. If the call stack is empty, the event loop dequeues functions from the callback queue and adds them to the call stack for execution.
Callback Queue
When an asynchronous operation completes, a callback function is pushed into the callback queue. The event loop ensures that these callbacks are executed in the correct order.
Debugging Execution Context and Call Stack
Understanding how to debug issues related to execution context and the call stack is essential for efficient JavaScript development. Modern browsers provide robust developer tools that enable developers to inspect and debug code execution.
Browser Developer Tools:
Utilize the "Sources" panel to set breakpoints, inspect variables, and step through code.
The call stack and current execution context are visible in the "Call Stack" and "Scope" panels.
Console Logging:
- Insert
console.logstatements strategically to log variable values and track the flow of execution.
- Insert
Debugger Statement:
- Place the
debuggerstatement in the code to pause execution and open the debugger when reached.
- Place the
function example() {
debugger;
// Code to be inspected
}
Conclusion
Understanding JavaScript execution context and the call stack is fundamental for writing efficient and bug-free code. Developers benefit from grasping how functions are invoked, how the call stack operates, and how asynchronous operations are managed through the event loop.


