Web APIs & Browser Runtime
The browser environment that powers JavaScript β from DOM manipulation and fetch to Web Workers and requestAnimationFrame.
JavaScript the language is surprisingly small. Most of what developers think of as 'JavaScript' β DOM manipulation, HTTP requests, timers, storage β are actually Web APIs provided by the browser. Understanding this boundary explains why the same JS code behaves differently in Node.js vs browsers.
JavaScript (the language) is just variables, functions, objects, and control flow. DOM manipulation, HTTP requests, timers, storage β these are all WEB APIs provided by the browser environment. They're NOT part of the ECMAScript specification.
While JS is single-threaded, the browser uses multiple threads: a network thread for fetch, a timer thread for setTimeout, a rendering thread for layout/paint, and worker threads for Web Workers. The Event Loop bridges these threads back to JS.
Unlike setTimeout, rAF is synced with the browser's display refresh rate (usually 60Hz). Callbacks run BEFORE the next paint, making it ideal for animations. The browser can also pause rAF in background tabs to save resources.
Web Workers run JavaScript on a separate thread. They can't access the DOM but can do heavy computation without blocking the main thread. Communication is via postMessage (data is copied, not shared β unless using SharedArrayBuffer).
Key Concepts
Runs JS, handles DOM updates, processes events, and executes rendering. Everything shares this thread, which is why long JS blocks UI.
Schedules a callback before the next paint. Runs at display refresh rate (~60fps). Automatically paused in background tabs.
Run JS in a background thread. No DOM access, communicate via messages. Ideal for heavy computation (image processing, data parsing).
Special workers that act as network proxies. Enable offline support, push notifications, and background sync. Part of Progressive Web App (PWA) patterns.
1// Web APIs are NOT JavaScript β they're browser-provided:23// DOM API β manipulate the document4document.querySelector("#app").innerHTML = "Hello";56// Fetch API β HTTP requests (runs on network thread)7const resp = await fetch("/api/data");8const data = await resp.json();910// setTimeout β delegates to the browser timer thread11setTimeout(() => console.log("delayed"), 1000);1213// Web Worker β true multi-threading14const worker = new Worker("heavy-task.js");15worker.postMessage({ data: largeArray });16worker.onmessage = (e) => console.log(e.data);1718// requestAnimationFrame β synced with display refresh19function animate() {20 updateCanvas();21 requestAnimationFrame(animate); // ~16ms (60fps)22}2324// IntersectionObserver β efficient scroll detection25const observer = new IntersectionObserver((entries) => {26 entries.forEach(e => {27 if (e.isIntersecting) loadImage(e.target);28 });29});
Knowing which APIs are browser-provided (not JavaScript) helps you: write isomorphic code, understand threading boundaries, optimize animations with rAF, offload work to Web Workers, and build offline-capable PWAs.
Common Pitfalls
1Smooth Animations with rAF
Your JavaScript animation uses setInterval(update, 16) to target 60fps. It works on your monitor but stutters on 120Hz displays and wastes CPU in background tabs.
setInterval isn't synced with the display refresh rate. On 120Hz displays, you only update every other frame (visual stuttering). In background tabs, the interval keeps firing (wasted CPU and battery).
Replace setInterval with requestAnimationFrame. rAF automatically matches the display's refresh rate (60Hz, 120Hz, 144Hz) and pauses when the tab is hidden. Use the timestamp parameter for frame-rate-independent animation.
Takeaway: requestAnimationFrame is the only correct way to do visual animations in JavaScript. It's display-synced, power-efficient, and provides a high-resolution timestamp for smooth, frame-independent motion.
2Offloading Heavy Computation to Web Workers
Your app parses a 50MB CSV file client-side. The UI freezes for 8 seconds during parsing β no spinner, no progress feedback. Users think the app crashed.
CSV parsing runs on the main thread, blocking all UI updates, event processing, and rendering. The browser can't even show a loading spinner because the rendering pipeline is blocked.
Move parsing to a Web Worker: const worker = new Worker('csv-parser.js'). Send the file via postMessage, receive parsed results async. The main thread stays responsive for UI updates and progress indication.
Takeaway: Any computation taking >50ms should be considered for a Web Worker. The 50ms threshold comes from the recommended budget for maintaining 60fps (16ms per frame, with time needed for rendering and event processing).