Understanding JavaScript Execution Context: The Foundation of Interactivity
DEV Community

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 var are initialised with undefined.
  • Functions defined with the function keyword are saved as a whole.
  • let and const variables 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.
  • โœ… const and let solved 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.