Modern JavaScript Mastery
JavaScript Execution Context
The Engine's Environment
Every line of JavaScript you write doesn't just run in a void. It's executed within a specific environment called an Execution Context. Think of it as a wrapper that the JavaScript engine creates to manage the code it's currently running. The most fundamental of these is the Global Execution Context (GEC), which is created the moment your script starts to run. Whether it's in a browser or a Node.js environment, the GEC is the base level where everything happens.
Every line of JavaScript runs inside an Execution Context — this is the foundation of how JS works.
Each execution context is handled in two distinct phases: the Creation Phase and the Execution Phase. Understanding this two-step process is key to demystifying some of JavaScript's most peculiar behaviors.
Creation vs. Execution
Before a single line of your code is executed, the JavaScript engine performs a quick scan in what's known as the Creation Phase. During this phase, the engine isn't running your logic. Instead, it's setting up the environment. It allocates memory for all the variables and functions defined in that scope.
This setup process is what leads to hoisting. The engine "hoists" declarations to the top of their scope. For functions, the entire function body is stored in memory. For variables declared with var, they are initialized with a default value of undefined. However, variables declared with let and const are also hoisted, but they are not initialized. Accessing them before their declaration results in a ReferenceError because they exist in a state known as the .
// Hoisting with 'var'
console.log(myVar); // Outputs: undefined
var myVar = 10;
console.log(myVar); // Outputs: 10
// Hoisting with 'let' and the TDZ
// console.log(myLet); // Throws ReferenceError: Cannot access 'myLet' before initialization
let myLet = 20;
console.log(myLet); // Outputs: 20
Only after the Creation Phase is complete does the Execution Phase begin. Now, the engine goes through your code line by line, executing it, assigning the actual values to variables, and invoking functions.
Stacking Up Contexts
When a function is called, the engine creates a new Function Execution Context (FEC). This new context is placed on top of the current one, forming what is known as the Call Stack. The call stack is a last-in, first-out (LIFO) structure. The function currently being executed is always at the top of the stack.
When a function finishes its execution, its context is popped off the top of the stack, and control returns to the context below it. This continues until the stack is empty, which happens when all the code in the Global Execution Context has been run.
But how does a function access variables from outside its own context? This is managed by the Scope Chain. Each execution context has a reference to its outer environment, creating a chain of scopes. This outer reference is determined by where the function was physically written in the code, a concept known as its . When the engine needs to find a variable, it first looks in the current context. If it can't find it, it follows the chain to the outer environment, and so on, all the way up to the Global Execution Context.
Alongside the call stack, the engine uses a to store all the objects and functions our application needs. While the call stack tracks where we are in the code, the heap is the unstructured memory pool where the actual data lives.
What About 'this'?
The this keyword is a common source of confusion in JavaScript. Its value is determined entirely by the execution context—specifically, how the function containing it was called. It's a dynamic reference that changes based on the invocation context, not where the function was defined.
| Invocation Context | this Value |
|---|---|
| Global Context (outside any function) | The global object (window in browsers) |
Object Method (obj.myMethod()) | The object (obj) that the method was called on |
Simple Function Call (myFunction()) | The global object (or undefined in strict mode) |
| Event Listener | The DOM element that the event was fired on |
Arrow Function (() => {}) | The this value of its surrounding lexical context |
Understanding these mechanics—the two-phase creation and execution, the call stack's LIFO nature, the scope chain, and the dynamic this binding—is the foundation for writing predictable, bug-free JavaScript. It's the difference between merely knowing the syntax and truly understanding how the language operates.
What is the foundational execution context that is created the moment a JavaScript script begins to run?
During the Creation Phase, what value is assigned to variables declared with the var keyword?