Static Scoping

JavaScript uses Static Scoping. This means variable visibility is determined by the physical location of the code in the source file. The scope of a variable is determined by where we type the function rather than where we call the function. Because it is based on the text structure the engine determines the scope hierarchy during the Creation Phase.


The Anatomy of an Environment

The Scope of a function is physically managed by an Execution Context. This frame is composed of two distinct Environment Records. The combination of these records linked via pointers to their parents forms the Scope Chain.

1. Variable Environment Record

This is the legacy storage. It tracks variables declared with var and formal function declarations. A Function Execution Context has only one Variable Environment. It ignores block levels and is scoped strictly to the function level.

2. Lexical Environment Record

This record was introduced in ES6 to support block-scoping. It tracks variables declared with let and const. While the Function Execution Context has a base Lexical Environment every time the engine enters a block such as an if statement or a for loop it creates a new nested Lexical Environment for that block.


The Physical Mechanism: Internal Pointers

The Scope Chain is a linked list of environments. It relies on two critical internal pointers to bridge the gap between the Stack and the Heap.

A. The [[Environment]] Pointer

This pointer is stored inside the Function Object in the Heap memory. It stores a reference to the Lexical Environment Record where the function was physically defined. This is the memory of the function. It ensures that even if a function is called later from a different location it remembers the variables of its parent. In the case of closures this pointer refers to the persistent Context Object in the Heap.

B. The outer Pointer

This pointer is stored in the active stack frame of an Execution Context. It stores a reference to the immediate parent lexical environment record. When a function is invoked the engine copies the value from the [[Environment]] pointer in the heap into the outer pointer of the new stack frame.


Variable Resolution: The Lookup Algorithm

When we try to access a variable the JavaScript Engine follows a strict algorithm to find the value.

  1. Local Check: The engine searches the current Lexical Environment of the active stack frame for let or const. If the variable is not found it checks the Variable Environment of that same frame for var or function declarations.
  2. The Walk: If the identifier is not found in the local frame the engine follows the outer pointer to the parent lexical environment record.
  3. Recurse: This process repeats by following successive outer pointers until the Global Execution Context is reached.
  4. Termination: The engine reaches the Global Scope. If the variable is not found it throws a ReferenceError.

Shadowing

Shadowing occurs when a variable in a local scope has the same name as a variable in an outer scope. The resolution algorithm finds the variable immediately in the local environment and stops searching which effectively hides the outer version.

let user = "Global";
 
function login() {
  let user = "Local";
  console.log(user);
}

📝 Summary Table

ConceptLocationPhysical Content
Variable EnvStack FrameStores var and function declarations for the whole function.
Lexical EnvStack FrameStores let and const for specific blocks.
[[Environment]]Function HeapReference to the parent Lexical Environment where the function was born.
outer pointerStack FrameReference used to walk up to the immediate parent environment record.