Browser performance: revision notes
Quick revision
Browser performance has several separate costs: fetching bytes, parsing and executing JavaScript, rendering the interface, and handling user input. Start with a reproducible slow interaction or load, record a trace, and optimize the work responsible for the delay.
Rendering pipeline
The browser parses HTML and CSS, calculates styles, lays out geometry, paints pixels, and composites layers. JavaScript can trigger more of this work. Alternating DOM writes with layout reads can force repeated synchronous layout; batch reads before writes. Large DOM trees and expensive paint effects can add cost even when React renders quickly. See How browsers populate a page.
Measure user-visible outcomes
- LCP: when the largest relevant content becomes visible; investigate slow server response, late image discovery, and render blocking.
- INP: responsiveness across interactions; investigate delayed input processing and long main-thread work.
- CLS: unexpected layout movement; reserve dimensions for media and avoid inserting content above existing content.
Good Core Web Vitals targets are LCP ≤ 2.5 s, INP ≤ 200 ms, and CLS ≤ 0.1, evaluated at the 75th percentile of visits. Combine field data with controlled lab traces; one Lighthouse score is not a complete measurement. See Web Vitals.
Choose an optimization based on the trace
- Reduce initial JavaScript with code splitting; postpone nonessential components.
- Move suitable CPU work to Web Workers, allowing input/rendering to continue.
- Use appropriately sized images and reserve layout space; see Image optimization.
- Virtualize long lists to limit mounted elements; see the React Performance lessons.
- Debounce repeated requests and throttle high-frequency updates according to the interaction's needs.
- Clean up timers, observers, listeners, and worker jobs so unused views do not retain work.
Event-loop revision
Tasks execute JavaScript to completion. Promise callbacks are microtasks, processed at checkpoints; an unbounded microtask chain can delay another task or a rendering opportunity. A zero-delay timeout does not run immediately. Splitting work into later tasks may permit rendering, but scheduling a promise continuation does not make a heavy calculation run in parallel.
The supplied checkout has no standalone browser-performance theory document. Its actual event-loop theory is copied below from the JavaScript Interview Series; the notes above are original revision material. The workers and image lessons include the corresponding repository implementations.
Original HackTheCode notes
Event loop
- Did you know that JavaScript is single-threaded? This means it can only do one thing at a time.
- But how does JavaScript handle multiple tasks, like making asynchronous calls and rendering pages smoothly? That's where the Call Stack & Event Loop comes into play!
- Let's start with the Call Stack. The Call Stack is stack data structure which means its first in last out. When a function is called, it's added to the top of the stack. Once it's done, it's removed from the stack.
- But what happens when you make an asynchronous call, like fetching data from an API? These calls are handled by another component like "Web APIs" (Browser) or "libuv library" (Node.js).
- Once they finishes their task (except promises), the callback is sent to the Event Queue, also known as the Task Queue.
- But Promises are handled with a higher priority. They go to the Micro-task Queue (Priority Queue), which ensures they are processed faster than tasks in the Event Queue.
- The Event Loop is the hero here. It keeps an eye on the Call Stack. When the Call Stack is empty, the Event Loop takes the next callback from the Event Queue or the Micro-task Queue and pushes it onto the Call Stack.
- Remember, blocking the Call Stack is a bad idea because it also handles rendering tasks as well. If the Call Stack is - blocked, your page can get stuck.
function logA() {
console.log("A");
}
function logB() {
console.log("B");
}
function logC() {
console.log("C");
}
function logD() {
console.log("D");
}
logA();
setTimeout(logB, 0);
Promise.resolve().then(logC);
logD();
Click here to visualize the above example.