The Big Picture: A Translator and a One-Person Desk

JavaScript is the programming language of web pages. It makes pages interactive: menus open, forms check themselves, maps move. Your CPU, however, understands only machine code (Chapter 2 — CPU Internals), not JavaScript text. The JavaScript engine is the part of the browser that reads JavaScript and gets the CPU to run it.

BrowserJavaScript engine
Chrome, Edge (Chromium), Node.jsV8
FirefoxSpiderMonkey
SafariJavaScriptCore

Two ideas run through this whole section:

  1. The engine translates your code smartly and quickly (3.1).
  2. JavaScript on a page runs on one main thread, like a single person at a desk. That person can only do one thing at a time (3.2).

3.1 Parsing and Compiling JavaScript (JIT Compilation)

What it is: Turning JavaScript source text into something the CPU can run. Modern engines do this in stages, so that starting is fast and hot code (code that runs again and again) becomes very fast.

 JavaScript text
      │
      ▼
 ┌─────────────┐   ┌─────────────┐   ┌───────────────┐
 │   PARSER    │──►│ SYNTAX TREE │──►│  INTERPRETER  │──► runs at once
 │ text→tokens │   │ (meaning of │   │ makes BYTECODE│    (starts fast,
 └─────────────┘   │  the code)  │   └───────┬───────┘     slower to run)
                   └─────────────┘           │ "this function is HOT"
                                             ▼
                                     ┌──────────────┐
                                     │ JIT COMPILER │──► optimised MACHINE
                                     └──────┬───────┘    CODE (very fast)
                                            │ "my guess was wrong!"
                                            ▼
                                  de-optimise: go back to bytecode

Step by step:

StepPlain meaning
ParseRead the text, check the grammar, build an abstract syntax tree (a tree of what the code means). Engines parse lazily: small functions you never call may be only quickly scanned, not fully processed.
InterpretTurn the tree into bytecode: a compact list of simple instructions, not tied to any CPU. An interpreter runs the bytecode immediately. Starting is quick, running is not the fastest.
ProfileWhile running, the engine counts which functions run often and which types of values they see (numbers, text...).
JIT compileJIT means Just-In-Time. Hot functions are compiled to real machine code for your CPU, using what was learned in profiling ("this variable has always been a number, so use fast number instructions").
De-optimiseIf a guess turns out wrong (the variable is suddenly text), the engine throws away the fast code and falls back to bytecode. Correctness always wins.

Why not compile everything first? A page has to start quickly. Heavy optimisation of code that runs once is a waste of time. So engines compile little at first and more as code proves it is hot. V8 has several tiers (stages) for this; the names of its tiers have changed between versions, so we do not rely on them. The same general plan is used in SpiderMonkey and JavaScriptCore.

Garbage collection (GC). In JavaScript you do not free memory yourself. The engine keeps track of objects and automatically frees those that nothing can reach any more. Like a cleaner who throws away only the things nobody is using. Modern collectors work in small pieces and in parallel to cause short pauses, but a pause can still happen. Memory you keep referencing (for example a huge list nobody removes) is never freed: this is a memory leak and makes the tab grow in the browser Task Manager (Section 1.4).

Security note. Machine code made at run time is a special case: normally memory is either writable or runnable, not both. JIT engines handle this carefully, and it is one reason the renderer is sandboxed (Section 1.3).

3.2 The Event Loop

What it is: The mechanism that lets one thread handle clicks, timers, network replies and drawing without waiting around.

The single main thread. In a renderer process, JavaScript for a page runs on one main thread (Chapter 10 — Threads). That same thread also does much of the work for style, layout and painting (Section 2). So the thread is shared between your code and the page's looks. If your code is busy, the page cannot respond.

The parts, with a café analogy:

PartIn the caféPlain meaning
Call stackThe order the barista is making right nowA pile of the functions currently running. A function that calls another puts it on top; when done it is removed.
Task queue (macrotasks)Customers waiting in lineEvents waiting for their turn: a click, a timer finishing (setTimeout), a network message.
Microtask queueA sticky note "do this right after this drink"Small follow-up jobs, such as the next step of a promise (.then). They run as soon as the current job ends, before the next task.
Web APIsThe coffee machine and the timer on the wallFeatures the browser provides outside JavaScript: timers, network requests (fetch), DOM events. They do their work in the background and drop a result in the queue.
Event loopThe managerKeeps checking: "stack empty? then take the next job."
        ┌────────────────────────────────────────────────────┐
        │                  MAIN THREAD                       │
        │                                                    │
        │   CALL STACK        ◄── run ──   EVENT LOOP        │
        │   (running now)                  "stack empty?     │
        │                                   microtasks first,│
        │                                   then next task,  │
        │                                   maybe draw a     │
        │                                   frame"           │
        └───────▲───────────────────────────────▲────────────┘
                │                               │
          MICROTASK QUEUE                  TASK QUEUE
          (promise steps)           (clicks, timers, network)
                ▲                               ▲
                └─────────── WEB APIs ──────────┘
                    timers, fetch, DOM events (browser does this
                    work in the background, then posts the result)

One turn of the loop (simplified):

  1. Take the next task from the queue and run it until the call stack is empty.
  2. Run all microtasks (including new ones added while running).
  3. If it is time for a new frame (about every 16.7 ms on a 60 Hz screen), do style, layout, paint (Section 2).
  4. Repeat.

A small example. What order do these lines print?

console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");

The answer is A, D, C, B. "A" and "D" are normal lines on the stack. The promise step "C" is a microtask, so it runs right after the current code. The timer "B" goes through the task queue, so even with 0 ms it must wait for the next turn. (console.log is a harmless command that prints text in the DevTools Console.)

Why a long script freezes the page. Because there is only one main thread, a task that runs for 2 seconds means the loop cannot take the next click, run the next animation frame or repaint for 2 seconds. The tab looks frozen though nothing crashed. Chrome and Edge may eventually show "Page unresponsive". A loop such as "count to a billion" on the main thread is a classic cause.

 main thread timeline (each box = one job)

 | click | style | draw |  VERY LONG SCRIPT (2 seconds)  | click?? | draw |
                         ^------- page frozen -----------^

Web Workers: more threads. To do heavy work without freezing the page, JavaScript can start a Web Worker: an extra thread (inside the same renderer process) that runs a script separately. A worker has no access to the DOM. It talks to the page by sending messages (copies of data), so the two never touch the same memory by accident. This avoids many of the dangers of sharing data between threads that you met in Chapter 10 — Threads (races and locks). Related tools include Service Workers (a background script that can handle network requests, see Section 5) and SharedArrayBuffer, which does share memory but is restricted by security rules.

Async code is the answer to waiting. A fetch() to a server may take 300 ms. If JavaScript waited, it would freeze the page. Instead it starts the request through a Web API and moves on. When the reply arrives, a promise step is queued. That is why web code is full of .then(...) and async/await: they are ways to say "do this later, when the answer is here."

Summary

TermPlain meaning
V8 / SpiderMonkey / JavaScriptCoreJavaScript engines of Chromium / Firefox / Safari
BytecodeCompact, CPU-neutral instructions run by the interpreter
JITCompile hot code to machine code while the page runs
De-optimiseThrow away fast code if its assumptions break
Garbage collectionAutomatic freeing of unreachable objects
Main threadOne thread for your JS and much page work
Call stackFunctions running right now
Task queueWaiting events: clicks, timers, network
Microtask queuePromise steps, run right after the current job
Event loopTakes the next job when the stack is empty
Web WorkerExtra thread without DOM access; talks by messages
Long taskA script that holds the main thread and freezes the page