Explain execution context in detail.
In JavaScript, an 'Execution Context' (EC) is an abstract concept that holds information about the environment where the current code is being executed. It's the mechanism that allows the JavaScript engine to manage the execution of code, track variables, and determine the value of 'this'. Every time a JavaScript file is loaded, a function is called, or an `eval` statement is used, a new execution context is created.
Types of Execution Contexts
There are primarily three types of Execution Contexts in JavaScript:
- Global Execution Context (GEC): This is the base/default execution context. It's created when the JavaScript file first loads in the browser or Node.js environment. There can only be one global execution context. It creates a global object (e.g.,
windowin browsers,globalin Node.js) and setsthisto refer to this global object. - Function Execution Context (FEC): A new function execution context is created whenever a function is called. Each function call creates its own unique execution context. It has its own scope and determines how
thisis bound within that function. - Eval Function Execution Context: This is created when code is executed inside an
eval()function. It's less commonly used and generally discouraged due to security and performance reasons.
Components of an Execution Context
Every Execution Context has several components that store vital information needed during execution:
1. Variable Environment (VE)
The Variable Environment is where variable declarations (var) and function declarations are stored. During the creation phase of an EC:
vardeclarations are initialized withundefined(this is the 'hoisting' ofvar).- Function declarations are fully stored in memory and are available for use.
It's important to note that let and const declarations are also hoisted, but they are not initialized with undefined in the Variable Environment. Instead, they are placed in a 'Temporal Dead Zone' (TDZ) until their actual declaration in the code is reached.
2. Lexical Environment (LE)
The Lexical Environment is a more general and fundamental concept than the Variable Environment, and it includes the Variable Environment. Every Lexical Environment has two main parts:
- Environment Record: This is where specific identifiers (variable names, function names, class names) and their corresponding values are stored for the current scope. It's further divided into:
* Declarative Environment Record: For function arguments,
functiondeclarations,var,let,const,class, andimportdeclarations. * Object Environment Record: Used for the Global EC (where thewindoworglobalobject's properties become available as global variables). - Outer Lexical Environment Reference: This is a reference to the lexical environment of the outer (parent) scope. This reference is crucial for the 'scope chain' mechanism, allowing inner scopes to access variables declared in outer scopes.
While var declarations are primarily associated with the Variable Environment, let and const are specifically managed by the Lexical Environment, including their Temporal Dead Zone behavior.
3. `this` Binding
The this binding determines the value of the this keyword within the execution context. Its value is established during the creation phase and can vary depending on how the code is called:
- Global Execution Context:
thisrefers to the global object (windowin browsers,globalin Node.js). - Function Execution Context: The value of
thisdepends on how the function is called. * Simple function call:thisrefers to the global object (orundefinedin strict mode). * Method call:thisrefers to the object on which the method was called. * Constructor call (usingnew):thisrefers to the newly created instance. * Explicit binding (call,apply,bind):thisis explicitly set.
The Execution Context Stack (Call Stack)
JavaScript is a single-threaded language, meaning it can execute only one task at a time. The 'Execution Context Stack' (also known as the Call Stack) is a LIFO (Last-In, First-Out) stack data structure that manages all the execution contexts created during the execution of a script.
When a script starts, the Global Execution Context is pushed onto the stack. Whenever a function is called, a new Function Execution Context is created and pushed onto the top of the stack. When a function finishes its execution, its context is popped off the stack, and control returns to the context below it.
function second() {
console.log('Inside second');
}
function first() {
console.log('Inside first');
second();
console.log('Back in first');
}
console.log('Global start');
first();
console.log('Global end');
// Call Stack visualization:
// 1. [Global EC]
// 2. [Global EC, first EC] (after first() is called)
// 3. [Global EC, first EC, second EC] (after second() is called)
// 4. [Global EC, first EC] (after second() finishes)
// 5. [Global EC] (after first() finishes)
// 6. [] (after Global EC finishes)
Phases of Execution Context Creation
The creation and management of an execution context happen in two distinct phases:
1. Creation Phase (Memory Creation Phase)
This phase occurs before any code is actually executed. The JavaScript engine performs the following steps:
- Creation of Lexical Environment: A new Lexical Environment is created for the EC.
- Creation of Variable Environment: This forms part of the Lexical Environment.
thisBinding: The value ofthisis determined and assigned.- Hoisting:
* Function declarations are placed into memory.
*
vardeclarations are scanned and initialized toundefined. *letandconstdeclarations are also 'hoisted' but are not initialized; they remain in the Temporal Dead Zone until their line of declaration is reached during the execution phase.
2. Execution Phase (Code Execution Phase)
Once the creation phase is complete, the JavaScript engine begins to execute the code line by line. During this phase:
- Variables are assigned their actual values (overwriting the
undefinedforvar). letandconstdeclarations are initialized with their values when their line of code is reached.- Functions are invoked, leading to the creation of new Function Execution Contexts.
- The code is interpreted and executed.
Example Walkthrough
let x = 10;
const y = 20;
function outer() {
let z = 30;
var a = 40;
function inner() {
let b = 50;
console.log(x, y, z, a, b);
}
inner();
}
outer();
Let's trace the execution contexts for the example above:
- Global Execution Context (GEC) - Creation Phase:
*
thisis bound towindow(orglobal). * Lexical Environment is created. *xis hoisted (TDZ). *yis hoisted (TDZ). *outer(function declaration) is fully stored in memory. - GEC - Execution Phase:
*
xis assigned10. *yis assigned20. *outer()is called. A new Function Execution Context forouteris created and pushed onto the stack. - Function Execution Context for
outer()- Creation Phase: *thisis bound towindow(default simple call). * Lexical Environment is created (with an outer reference to GEC). *zis hoisted (TDZ). *a(var) is hoisted and initialized toundefined. *inner(function declaration) is fully stored in memory. - FEC for
outer()- Execution Phase: *zis assigned30. *ais assigned40. *inner()is called. A new Function Execution Context forinneris created and pushed onto the stack. - Function Execution Context for
inner()- Creation Phase: *thisis bound towindow. * Lexical Environment is created (with an outer reference toouterFEC). *bis hoisted (TDZ). - FEC for
inner()- Execution Phase: *bis assigned50. *console.log(x, y, z, a, b)is executed. *bis found ininner's LE. *a,zare found by traversing the scope chain toouter's LE. *x,yare found by traversing the scope chain to GEC's LE. *inner()finishes, its EC is popped from the stack. - Back in FEC for
outer()- Execution Phase: *outer()finishes, its EC is popped from the stack. - Back in GEC - Execution Phase: * The global script finishes, and the GEC is popped from the stack.
Understanding execution contexts is fundamental to grasping concepts like hoisting, scope, closures, and the behavior of the this keyword in JavaScript.