Browser resources have owners and lifetimes. An event listener, observer, object URL, timer, subscription, or worker can outlive the screen that created it unless the route or component releases it. The result may be repeated handlers, detached DOM retained in memory, stale network work, or a preview image URL that remains allocated after a case closes. Define ownership at creation: the case page owns its scroll observer and preview URL, while a shared application shell may own a connection across routes. Cleanup must be symmetric and safe when a task is interrupted halfway through setup.
Browser Memory and Resource Lifecycles
Working case
Reviewer 29 opens and closes the same photo preview twenty times. Each opening creates an object URL and a resize observer; neither is released on close. The page still seems functional, but memory rises and later resize events run twenty callbacks. Bind the observer and preview URL to one view lifetime. When the panel closes or changes case, disconnect the observer, revoke the object URL, cancel pending work, and remove listener registrations. Keep the image's source change and URL revocation in a safe order so the visible preview is not invalidated early.
Implementation boundary
function mountPreview(previewNode, imageFile) {
const previewUrl = URL.createObjectURL(imageFile);
const resizeObserver = new ResizeObserver(() => updatePreviewLayout(previewNode));
previewNode.src = previewUrl;
resizeObserver.observe(previewNode);
return () => {
resizeObserver.disconnect();
previewNode.removeAttribute("src");
URL.revokeObjectURL(previewUrl);
};
}The returned function is the owner cleanup. A production view must call it exactly once when the preview is removed or replaced; it should also handle errors during setup and any pending image decode or worker task. A shared AbortController can manage several event listeners or requests tied to the same view, but not every API accepts an abort signal. Keep explicit cleanup for observers and object URLs. Do not attach a listener directly to every row when one delegated listener on a stable parent meets the interaction need.
Cost and boundaries
One observer and one object URL are small, yet leaks accumulate with repeated navigation and large image files. Explicit disposal adds code and test cases, while accidental retention makes long sessions slower and harder to diagnose. A heap snapshot can show detached nodes and retained references; compare after repeated open/close cycles rather than judging one page load. Event delegation can reduce listener count, but a global listener still needs a stable owner and a route-aware callback so it does not act on obsolete UI.
Failure trace
A component unmounts its image but leaves the object URL and observer active. Manual testing opens the preview only once, so it passes. A reviewer working all day sees increasing memory use and duplicate layout updates. Reproduce with repeated cycles, inspect observer callbacks and memory retention, then invoke cleanup at the view boundary. Another failure is revoking the URL before the replacement image has loaded, causing a blank preview; sequence replacement and cleanup according to the displayed asset's ownership.
Verification
- Thirty open/close cycles do not multiply observer callbacks or retain old preview URLs.
- Replacing a preview releases the old asset without blanking the new one.
- Route exit stops listeners, observers, and pending work tied to that route.
Practice drill
Open and close a preview thirty times while recording observer callback count and memory trend. After each close, verify that no callback runs for the former node and that its object URL is revoked. Replace one image without closing the panel and confirm the old URL is released only after the new state owns the display. Add route navigation during image decode, then verify abortable work and observers stop without leaving a broken visible state.
Decision note
Create browser resources with an owner and a disposal path in the same code review; test repeated use, not only first mount.
Common Mistakes
- Assuming unmounting a DOM node releases every attached resource.
- Revoking an object URL before the displayed image no longer needs it.
- Using a global listener for a route task without route-aware cleanup.
Connected lessons
Browser Execution and Resource Lifecycle; Event Loop Tasks, Microtasks, and Paint; Workers, Messages, Transfer, and Cancellation; Cooperative Main-Thread Scheduling; React Effects and Request Races; Workers, Messages, Transfer, and Cancellation; Safe File Upload Pipeline.
Apply and check
Build Project: responsive import worker and review Web Development: layout and runtime contracts quiz.
