Understanding JavaScript Execution Context: The Foundation of Interactivity
Creating contexts
There are two ways to create this workspace:
- When a program starts (Global execution context)
- Every time a function is called (Function execution context)
const name = "Alex";
function greet() {
const message = "Hello";
console.log(message);
}
greet();
When this program starts, JS creates the global context with name and greet. Then the greet function is called, which creates a new execution context that includes the variable message. When the function greet() finishes its execution, this context is removed. When the program finishes, the global context is also removed.
A fair question here is: "removed from where"? The answer is: from the infamous call stack.
The call stack
These execution workspaces are stored in a stack. If you don't know what a stack is: it is a data structure with some specific properties. New elements are added at the top of the stack; elements are removed also from the top of the stack. The most famous comparison is to think of a stack as a stack of plates. You wouldn't remove a plate from the bottom of the stack. This property is called LIFO: Last in First out.
โโโโโโโโโโโโโโโ
โ greet() โ โ Top (will be removed first)
โโโโโโโโโโโโโโโค
โ Global โ โ Bottom (removed last)
โโโโโโโโโโโโโโโ
When greet() finishes, it's "popped" from the stack and only the global context remains (until the end of this program).
function first() { second(); }
function second() { third(); }
function third() {}
- Before any call:
โโโโโโโโโโโโโโโโ
โ Global โ
โโโโโโโโโโโโโโโโ
- During execution:
โโโโโโโโโโโโโโโโ
โ third() โ
โโโโโโโโโโโโโโโโค
โ second() โ
โโโโโโโโโโโโโโโโค
โ first() โ
โโโโโโโโโโโโโโโโค
โ Global โ
โโโโโโโโโโโโโโโโ
- After all finish:
โโโโโโโโโโโโโโโโ
โ Global โ
โโโโโโโโโโโโโโโโ
As every function finishes its execution, it gets popped from the stack. First third() finishes and it is removed, then second(), etc.
The two phases of the context
Every execution context has two phases. The 1st phase is the Creation: JS scans the code and prepares the memory; it identifies variables and functions.
- Variables declared with
varare initialised withundefined. - Functions defined with the
functionkeyword are saved as a whole. letandconstvariables are registered in another place called the Temporal Dead Zone.
The next phase is the Execution: JS runs the code line-by-line. It assigns values to variables and executes statements.
Why hoisting happens?
There is a word that scares every web developer: hoisting. This happens due to the creation phase. As JS scans the code, it already knows about variables and functions before execution even begins. That process is called hoisting.
console.log(a); // Prints undefined, because JS initialized `a` with undefined in the creation phase
var a = 5;
Function declarations are fully hoisted, so you can call them even before they are written.
greet(); // This works! Prints "Hello"
function greet() {
console.log("Hello");
}
Note: function expressions are NOT hoisted. Function expressions are functions that are stored in a variable.
let and const against hoisting
In ES6, the keywords let and const were introduced. When you run the same code as before but using let this time... you get a reference error. What is happening?
console.log(a); // throws ReferenceError
let a = 5;
JavaScript still knows that a exists, but the difference is that now it is not saved in the context. Instead, it's stored in another place called the Temporal Dead Zone (TDZ).
Understanding TDZ
The TDZ is a region in a block where a variable exists but cannot be accessed (yet). TDZ starts from the beginning of the block until the variable is declared and initialised.
if (true) {
// TDZ for 'a' starts here
console.log(a); // ReferenceError - 'a' is in TDZ, not accessible
let a = 5; // TDZ ends here, 'a' is now initialised and accessible
console.log(a); // 5 - now it works
}
The Scope Chain: Looking Beyond the Current Context
When you reference a variable inside a function, JS doesn't only look in the current execution context. If the variable is not found in the current context, it starts looking in the parent, and then its parent, until it reaches the global context. This is called the scope chain.
const global = "I'm a global var";
function outer() {
const outerVar = "I'm in outer scope";
function inner() {
const innerVar = "I'm in inner scope";
console.log(innerVar); // โ
Found in inner's context
console.log(outerVar); // โ
Found in outer's context (via scope chain)
console.log(global); // โ
Found in global context (via scope chain)
}
inner();
}
outer();
Summary
- โ JS creates an execution context before running code.
- โ Every function call creates an execution context.
- โ These contexts are managed using the call stack.
- โ Each execution context has a creation and an execution phase.
- โ Hoisting happens in the creation phase due to the scan of the code.
- โ
constandletsolved the initialisation issues in JS by storing variables in TDZ. - โ The scope chain allows functions to access variables from their parent contexts (lexical scoping).
Comments
No comments yet. Start the discussion.