post's image

Daily Depth #6: JavaScript's Execution Phases - From Code to Action

Ghost wrote 5 days ago (Aug 5, 2025) with 16👁️ | 8 mins read

Welcome back to Daily Depth! Today, we're embarking on a fascinating journey to understand what truly happens behind the scenes when your JavaScript code runs. We'll break down the intricate multi-phase process that the JavaScript engine (like V8 in Chrome or SpiderMonkey in Firefox) goes through to transform your `.js` files into executable instructions.

Understanding these phases is critical for grasping concepts like hoisting, scope, and closures, and for debugging your code more effectively.

JavaScript's Execution Model: A High-Level Overview

At a high level, when you run a JavaScript file or script, the engine goes through several distinct phases for each Execution Context it creates. An execution context is essentially an environment where JavaScript code is evaluated and executed. The most common types are:

  • Global Execution Context: The base context for any JavaScript code.
  • Function Execution Context: Created whenever a function is called.
  • Eval Execution Context: Created when code is executed inside eval(). (Less common and generally discouraged.)

For each of these contexts, the engine typically follows these phases:

  1. Parsing/Compilation Phase (Creation Phase for ES5/Early JS):
    • Lexical Analysis (Tokenization)
    • Parsing (Syntax Analysis & AST Generation)
  2. Execution Phase:
    • Setup of the Lexical Environment (Variable Environment & Outer Environment Reference)
    • Code Execution

Let's break down each one.


Phase 1: Parsing/Compilation (Before Execution)

Before a single line of your code executes, the JavaScript engine meticulously analyzes it. This phase is sometimes referred to as the "Creation Phase" in older JavaScript contexts (like ES5 explanations of hoisting), but modern engines do more than just creation; they compile.

a. Lexical Analysis (Tokenization)

The very first step. The engine reads your raw code character by character and breaks it down into a stream of meaningful chunks called tokens. Tokens are the smallest building blocks of the language, like keywords (function, let, const), identifiers (myVariable, sum), operators (+, =, *), strings ("hello"), numbers (123), etc.

Example: Code: let x = 10 + y; Tokens: let, x, =, 10, +, y, ;

b. Parsing (Syntax Analysis & Abstract Syntax Tree - AST Generation)

Once tokens are generated, the parser takes this stream of tokens and checks for grammatical correctness according to JavaScript's syntax rules. If the syntax is valid, it transforms the tokens into a tree-like structure called an Abstract Syntax Tree (AST).

The AST represents the structural or syntactic relationships within the code. It doesn't contain every single detail (like comments or whitespace), but it captures the essential structure needed for the engine to understand what your code is trying to do.

Example (simplified AST conceptualization for let x = 10 + y;):

VariableDeclaration
  kind: "let"
  declarations:
    - VariableDeclarator
      id: Identifier (name: "x")
      init: BinaryExpression
        operator: "+"
        left: NumericLiteral (value: 10)
        right: Identifier (name: "y")

The AST is crucial because it's what the engine will eventually use to generate executable code.

(Just-In-Time Compilation - JIT)

Modern JavaScript engines employ Just-In-Time (JIT) compilation. This means that after the AST is generated, it's often transformed into intermediate bytecode, and then further optimized and compiled into highly efficient machine code just before or during execution. This hybrid approach allows for both quick startup times (interpreting initially) and high performance (compiling hot code paths).


Phase 2: Execution

After the parsing/compilation phase, the engine moves into the execution phase. This is where the code actually runs, variables are assigned values, functions are called, and operations are performed.

a. Creation of Execution Context (Environment Setup)

Before execution, an Execution Context is created. This involves several key steps that establish the environment for the code to run:

  • Lexical Environment Creation: This is where the magic of scope happens. Each execution context gets its own Lexical Environment, which is made up of two main components:
    • Environment Record: This is a place where all the variable and function declarations within the current scope are stored.
      • For var declarations and function declarations, they are initialized with undefined (for var) or actual function objects (for function declarations) during this creation phase. This is the mechanism behind hoisting.
      • For let and const declarations, they are also "hoisted" but are placed in a "Temporal Dead Zone" (TDZ) and not initialized until their actual line of code is reached in the execution phase. This is why you get an error if you try to access them before their declaration.
    • Outer Environment Reference: A reference to the lexical environment of its outer (parent) scope. This is how the engine determines the scope chain and enables closures (as discussed in Daily Depth #2!).
  • this Binding: The this keyword's value is determined for the current execution context. (This depends on how the function was called).

b. Code Execution

Finally, the JavaScript engine begins executing the code line by line.

  • Variable Assignments: Variables declared earlier are now assigned their actual values.
  • Function Calls: When a function is called, a new Function Execution Context is created and pushed onto the Call Stack (as discussed in Daily Depth #1!). The entire parsing and environment setup process repeats for this new context.
  • Operations: All arithmetic, logical, and other operations are performed.
  • Side Effects: Interactions with the DOM, network requests, console logs, etc., occur.

Once a function's execution context finishes, it's popped off the call stack, and control returns to the previous context.


A Simplified Flow for a Global Script:

  1. Global Execution Context Creation:
    • Scan code for var and function declarations. Hoist them (allocate memory, var to undefined, function to function object).
    • Scan code for let and const declarations. Hoist them to TDZ.
    • Determine this (global object).
    • Set Outer Environment Reference to null (for global context).
  2. Global Code Execution:
    • Execute code line by line.
    • Assign values to var, let, const.
    • When a function is called, repeat step 1 for the new Function Execution Context.
    • When the global code finishes, the Global Execution Context is popped off the call stack.

Why Does This Matter?

  • Hoisting: You now know why you can call a function before its declaration (it's hoisted and initialized) but can't access a let variable before its declaration (it's in the TDZ even though it's "hoisted").
  • Scope: The Lexical Environment and Outer Environment Reference clearly explain how JavaScript resolves variables within nested scopes and why functions remember the scope they were defined in (closures).
  • Debugging: When you understand these phases, you can better predict how your code will behave and pinpoint issues related to variable accessibility and execution flow.
  • Performance: The JIT compilation step is why modern JavaScript is so fast, constantly optimizing the code as it runs.

Conclusion

The journey of your JavaScript code from a plain text file to executable instructions is a complex, multi-phase process orchestrated by the JavaScript engine. From lexical analysis and parsing to the meticulous setup of execution contexts and the dynamic dance of the call stack, each phase plays a vital role. By peering "under the hood" and understanding these foundational mechanisms, you gain a deeper appreciation for JavaScript's power and its sometimes quirky behaviors, empowering you to write more robust and performant applications.

Stay tuned for the next episode of Daily Depth!