Execution Environment and Runtime Mechanisms
Execution Context Stack and Call Stack
JavaScript’s Execution Context Stack (also known as the call stack) is the core mechanism for managing code execution order. Each time a function is called, a new execution context is created and pushed onto the stack. When the function completes, its context is popped off the stack.
function first() {
console.log('First function');
second();
}
function second() {
console.log('Second function');
third();
}
function third() {
console.log('Third function');
}
first();Call stack changes during execution:
- Global execution context is pushed onto the stack.
firstfunction execution context is pushed.secondfunction execution context is pushed.thirdfunction execution context is pushed.thirdcompletes, its context is popped.secondcompletes, its context is popped.firstcompletes, its context is popped.- Global execution context is popped.
Variable Environment and Lexical Environment
An execution context consists of a Variable Environment and a Lexical Environment:
- Variable Environment: Stores
var-declared variables and function declarations. - Lexical Environment: Stores
let/const-declared variables, enabling block-level scoping.
function example() {
var a = 1; // Stored in variable environment
let b = 2; // Stored in lexical environment
const c = 3; // Stored in lexical environment
if (true) {
var d = 4; // Still in variable environment
let e = 5; // New lexical environment
const f = 6; // New lexical environment
}
console.log(d); // 4, var ignores block scope
// console.log(e); // ReferenceError, e is not visible outside
}Scope Chain and Identifier Resolution
Identifier resolution follows the scope chain, searching upward:
- Check the current lexical environment.
- If not found, check the parent lexical environment.
- Continue up to the global environment.
- If not found in the global environment, throw a
ReferenceError.
let globalVar = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(innerVar); // Current lexical environment
console.log(outerVar); // Parent lexical environment
console.log(globalVar); // Global environment
}
inner();
}
outer();Garbage Collection Mechanism
Mark-and-Sweep Algorithm
The V8 engine primarily uses the mark-and-sweep algorithm for garbage collection:
- Mark Phase: Starting from root objects (global variables, variables in the current execution context, etc.), mark all reachable objects.
- Sweep Phase: Traverse heap memory, reclaim unmarked objects.
// Example: Object reference relationships
let obj1 = { name: 'Object 1' }; // Root-reachable
let obj2 = { name: 'Object 2' }; // Root-reachable
obj1.ref = obj2; // obj1 references obj2
obj2.ref = obj1; // obj2 references obj1, forming a circular reference
// Even if obj1 and obj2 become unreachable from the root, traditional reference counting cannot reclaim them
obj1 = null;
obj2 = null;
// Mark-and-sweep can reclaim them, as they are no longer reachable from the rootGenerational Garbage Collection
V8 divides heap memory into new space and old space:
- New Space: Stores short-lived objects, using the Scavenge algorithm.
- Old Space: Stores long-lived objects, using mark-and-sweep and mark-and-compact algorithms.
// New space object example
function createShortLivedObjects() {
for (let i = 0; i < 10000; i++) {
let temp = { data: i }; // New space object
// Becomes unreferenced soon after use
}
}
// Old space object example
let longLivedObject = { data: 'persistent' };
function updateLongLivedObject() {
longLivedObject.data = 'updated'; // Persists long-term
}Common Memory Leak Patterns
- Accidental Global Variables:
function leak() {
leakedVar = 'This is a global variable'; // Accidentally creates a global variable
}- Forgotten Timers or Callbacks:
let someResource = getData();
setInterval(() => {
const node = document.getElementById('node');
if (node) {
node.innerHTML = JSON.stringify(someResource);
}
}, 1000);
// someResource remains referenced even if no longer needed- Closures Retaining Large Objects:
function outer() {
const bigData = new Array(1000000).fill('data');
return function inner() {
console.log('This closure keeps bigData in memory');
};
}
const closure = outer(); // bigData cannot be reclaimedV8 Engine’s Just-In-Time (JIT) Compilation
V8 Compilation Pipeline
V8 uses a multi-tier compilation strategy:
- Ignition Interpreter: Quickly generates bytecode.
- TurboFan Compiler: Compiles hot code into optimized machine code.
- Deoptimization: Reverts to the interpreter when assumptions fail.
function add(a, b) {
return a + b;
}
// Initial execution: Ignition interpreter generates bytecode
add(1, 2);
// After repeated calls: TurboFan compiles to optimized machine code
for (let i = 0; i < 100000; i++) {
add(i, i + 1);
}
// If non-numeric arguments are passed:
add('1', '2'); // May trigger deoptimizationHidden Classes and Inline Caching
V8 uses hidden classes to optimize object property access:
function Point(x, y) {
this.x = x;
this.y = y;
}
const p1 = new Point(1, 2); // Creates a hidden class
const p2 = new Point(3, 4); // Reuses the same hidden class
// Adding a new property creates a new hidden class
p1.z = 3; // p1 now has a new hidden classInline caching optimizes property access:
function getProp(obj) {
return obj.x; // First call records the hidden class
}
const obj1 = { x: 1 };
getProp(obj1); // Records obj1’s hidden class
const obj2 = { x: 2 }; // Same hidden class
getProp(obj2); // Directly uses cached access pathInlining Optimization
TurboFan inlines hot functions to reduce call overhead:
function square(x) {
return x * x;
}
function calculate() {
let sum = 0;
for (let i = 0; i < 1000; i++) {
sum += square(i); // May be inlined as sum += i * i
}
return sum;
}



