No history yet

Execution Context and Scope

How JavaScript Runs Your Code

When you run a JavaScript file, the engine doesn't just read it from top to bottom. Before a single line of your code is executed, JavaScript sets up the environment it will run in. This environment is called an Execution Context.

Every line of JavaScript runs inside an Execution Context — this is the foundation of how JS works.

Think of an execution context as a container. It holds two key things: a reference to the variables and functions in the current scope, and a special keyword called this.

The first container created is the Global Execution Context (GEC). This is the default context for code that isn't inside any function. It creates a global object—window in web browsers, global in Node.js—and sets the this keyword to point to it.

Creation and Execution

Every time you call a function, a new Function Execution Context (FEC) is created and stacked on top of the current context. Each context is created in two phases.

1. Creation Phase: The engine scans the code, sets aside memory for all variables and functions, and establishes their scope.

2. Execution Phase: The engine runs the code line by line, assigning values to variables and executing function calls.

During the Creation Phase, the engine performs an action called hoisting. It 'hoists' variable and function 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 the value undefined.

console.log(greeting); // Logs: undefined
var greeting = "Hello, world!";

sayHello(); // Logs: "Hello!"
function sayHello() {
  console.log("Hello!");
}

However, let and const behave differently. They are also hoisted, but they are not initialized. The space between the start of their scope and their declaration is called the Temporal Dead Zone (TDZ). Trying to access them in the TDZ results in a ReferenceError.

// This will throw a ReferenceError
console.log(message); 

let message = "This is in the TDZ.";

This prevents you from accidentally using a variable before its value has been set.

The Call Stack

JavaScript is single-threaded, meaning it can only do one thing at a time. It uses a call stack to manage execution contexts. The call stack is a data structure that works on a Last-In, First-Out (LIFO) principle.

When a script starts, the Global Execution Context is pushed onto the stack. When a function is called, its Function Execution Context is pushed on top. When the function finishes, its context is popped off the stack, and control returns to the context below it.

Consider this code:

function second() {
  console.log('Inside second');
}

function first() {
  console.log('Calling second');
  second();
  console.log('Finished second');
}

first();

When this runs, the call stack will look like this:

  1. GEC is pushed.
  2. first() is called, so first() FEC is pushed on top of GEC.
  3. second() is called from inside first(), so second() FEC is pushed on top.
  4. second() finishes and is popped off.
  5. first() finishes and is popped off.
  6. The script ends, and GEC is popped off.

Scope Chain and Lexical Environment

So, how does JavaScript know which variables a function can access? This is determined by its Lexical Environment.

A lexical environment is created with every execution context. It's a structure that holds all the local variables and a reference to the lexical environment of its parent. This parent-child link is what creates the scope chain.

JavaScript uses lexical scoping (or static scoping), which means a function's scope is determined by where it is written in the source code, not where it is called. When your code tries to access a variable, the engine first looks in the current function's scope. If it doesn't find it, it follows the link to the parent's scope and looks there. This continues all the way up to the global scope. If the variable isn't found anywhere, you'll get a ReferenceError.

This chain is why a nested function can access variables from its containing functions, but not the other way around. It's also the foundation for more advanced concepts like closures, which you'll explore later.

Now, let's test your understanding of these core mechanics.

Quiz Questions 1/6

What is the primary role of a JavaScript Execution Context?

Quiz Questions 2/6

In JavaScript, the call stack operates on a 'Last-In, First-Out' (LIFO) principle.

Understanding how the JavaScript engine sets up and executes your code is a huge step. This mental model of execution contexts, the call stack, and the scope chain will help you debug code more effectively and grasp more complex topics ahead.