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.
| Browser | JavaScript engine |
|---|---|
| Chrome, Edge (Chromium), Node.js | V8 |
| Firefox | SpiderMonkey |
| Safari | JavaScriptCore |
Two ideas run through this whole section:
- The engine translates your code smartly and quickly (3.1).
- 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:
| Step | Plain meaning |
|---|---|
| Parse | Read 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. |
| Interpret | Turn 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. |
| Profile | While running, the engine counts which functions run often and which types of values they see (numbers, text...). |
| JIT compile | JIT 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-optimise | If 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:
| Part | In the café | Plain meaning |
|---|---|---|
| Call stack | The order the barista is making right now | A 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 line | Events waiting for their turn: a click, a timer finishing (setTimeout), a network message. |
| Microtask queue | A 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 APIs | The coffee machine and the timer on the wall | Features 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 loop | The manager | Keeps 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):
- Take the next task from the queue and run it until the call stack is empty.
- Run all microtasks (including new ones added while running).
- If it is time for a new frame (about every 16.7 ms on a 60 Hz screen), do style, layout, paint (Section 2).
- 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
| Term | Plain meaning |
|---|---|
| V8 / SpiderMonkey / JavaScriptCore | JavaScript engines of Chromium / Firefox / Safari |
| Bytecode | Compact, CPU-neutral instructions run by the interpreter |
| JIT | Compile hot code to machine code while the page runs |
| De-optimise | Throw away fast code if its assumptions break |
| Garbage collection | Automatic freeing of unreachable objects |
| Main thread | One thread for your JS and much page work |
| Call stack | Functions running right now |
| Task queue | Waiting events: clicks, timers, network |
| Microtask queue | Promise steps, run right after the current job |
| Event loop | Takes the next job when the stack is empty |
| Web Worker | Extra thread without DOM access; talks by messages |
| Long task | A script that holds the main thread and freezes the page |