Skip to the chapter
←JavaScriptGGroundworkJSJavaScript
JavaScript/Intermediate/I10
27 / 41 written chapters
/

The cover
  1. B1Setup & mental model
  2. B2Execution context
  3. B3var, let & const
  4. B4Scope
  5. B5Single thread
  6. B6In the browser
  7. B7Types & values
  8. B8Strings & Math
  9. B9Operators & flow
  10. B10Functions
  11. B11Objects & arrays
  12. B12this
  13. B13Closures
  14. B14DOM & events
  15. B15Callbacks & promises
  16. B16Errors & tools
  17. B17Guided project
  18. ★Cheat page
  1. I1Scope & functions, properly
  2. I2Objects deeply
  3. I3Prototypes & OOP
  4. I4Async, properly
  5. I5Modules & tooling
  6. I6Regex & dates
  7. I7Error handling
  8. I8Real-time connections
  9. I9Offline & storage
  10. I10Browser APIs
  11. I11Modern JS
  12. ★Cheat page
  1. A1Engine & memory
  2. A2Advanced async
  3. A3Metaprogramming
  4. A4Types & data
  5. A5Patterns & architecture
  6. A6Performance
  7. A7Security
  8. A8Ecosystem
  9. A9Testing
  10. A10Observability
  11. A11Interview bank
  12. A12Build it yourself
  13. ★Cheat page

JSI10 · Part 2 · Intermediate

Browser APIs, in depth

Storage, the URL bar, and the event system — past what the beginner DOM chapter reached.

5 min read9 sections2 exercises
27 of 41

In this chapter

  • Browser storage
  • URL and URLSearchParams
  • History and IntersectionObserver, briefly
  • Events, in depth — live
  • Delegation and custom events

and 4 more

about 5 min
09On this page
  1. Browser storage
  2. URL and URLSearchParams
  3. History and IntersectionObserver, briefly
  4. Events, in depth — live
  5. Delegation and custom events
  6. Clipboard — reading and writing outside the page
  7. Page Visibility — knowing when nobody's looking
  8. BroadcastChannel — talking to your own other tabs
  9. Web Share and Geolocation, briefly

Browser storage

localStorage sessionStorage
Survivesclosing the tab, the browser, the computer restartingonly this tab; gone when it closes
Shared across tabs?yes, same originno — each tab gets its own
Capacity~5-10MB, string values onlysame
localStorage.setItem("theme", "dark");
localStorage.getItem("theme");     // "dark" — always a string
localStorage.setItem("user", JSON.stringify({ name: "Ana" }));
JSON.parse(localStorage.getItem("user"));   // objects need to round-trip through JSON yourself
localStorage.removeItem("theme");

URL and URLSearchParams

const url = new URL("https://shop.example.com/search?q=js&page=2");
console.log(url.pathname);              // what happens?
console.log(url.searchParams.get("q")); // what happens?

url.searchParams.set("page", "3");
console.log(url.toString());   // what happens?

A parsed URL gives every piece (pathname, hostname, protocol) as its own property, and searchParams is a live, mutable view — editing it and reading url.toString() again reflects the change immediately, no manual query-string concatenation required.

Dry run: parsing and mutating the URL step by step

StepCodeResult
1new URL("https://shop.example.com/search?q=js&page=2")parses into pieces — pathname is "/search", searchParams holds q=js and page=2
2url.pathname"/search" — just the path, no query string
3url.searchParams.get("q")"js"
4url.searchParams.set("page", "3")mutates the live searchParams view in place — page goes from 2 to 3
5url.toString()"https://shop.example.com/search?q=js&page=3" — the change shows up automatically

Step 4 never touches url.href or url.search directly — searchParams is a live view onto the same URL object, so mutating it is enough for step 5's toString() to reflect the change.

History and IntersectionObserver, briefly

history.pushState({ page: 2 }, "", "/products?page=2");  // changes the URL bar, no page reload
window.addEventListener("popstate", (e) => {
  console.log("back/forward pressed, state:", e.state);   // fires on browser back/forward, not on pushState itself
});

This is the mechanism every client-side router (React Router, Next.js's own routing) is built on — a URL that changes without a real navigation, plus a way to hear when the user manually goes back or forward.

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) console.log(entry.target, "scrolled into view");
  });
});
document.querySelectorAll(".lazy-image").forEach((img) => observer.observe(img));

The standard, efficient way to know when an element enters or leaves the viewport — infinite scroll, lazy-loaded images, and "animate in on scroll" effects all run on this instead of a scroll listener doing math on every single pixel of scrolling.

Events, in depth — live

Click the innermost box below and watch the log. Every listener here is a real addEventListener call against the actual nested boxes on this page.

Bubbling, capturing, and stopPropagation
outer
middle
inner — click me

With the capture checkbox off (the default), clicking "inner" logs inner → middle → outer — the event starts at the exact element clicked and bubbles upward through every ancestor listening for it. Check the capture box and it reverses to outer → middle → inner — capture-phase listeners run on the way down, before the event even reaches its target. Check "stopPropagation" and only the inner listener fires at all — the click never continues past it in either direction.

Delegation and custom events

Bubbling is what makes event delegation work: one listener on a parent container, instead of one per child, checking event.target to see which child was actually clicked.

list.addEventListener("click", (e) => {
  const item = e.target.closest("li");    // works even if the click landed on a span INSIDE the li
  if (!item) return;
  console.log("clicked:", item.dataset.id);
});
// one listener handles every current AND future <li> — no re-binding when items are added later
const updated = new CustomEvent("cart:updated", { detail: { count: 3 } });
cartElement.dispatchEvent(updated);

cartElement.addEventListener("cart:updated", (e) => {
  console.log("new count:", e.detail.count);
});

A custom event bubbles and can be listened for exactly like a real browser event — the standard way for one part of a page to announce something happened without being directly wired to whoever might care.

Clipboard — reading and writing outside the page

await navigator.clipboard.writeText("copied!");        // should run from a real user gesture (a click), not on page load

button.addEventListener("click", async () => {
  const text = await navigator.clipboard.readText();   // asks permission the first time
  console.log("clipboard had:", text);
});

Both methods are async and both are gated behind a secure context (HTTPS or localhost) and, for anything beyond a same-origin write, a permission prompt. Reading the clipboard specifically is the more restricted half — a page silently reading whatever's on a user's clipboard on load would be a real privacy problem, so the browser insists on both a secure context and, in most browsers, the read happening inside a direct response to user input.

Page Visibility — knowing when nobody's looking

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") {
    pauseVideo();
    stopPolling();
  } else {
    resumePolling();
  }
});

A tab that's minimized, in the background, or on a phone whose screen just locked doesn't stop running JavaScript — timers keep firing — but the user isn't watching. visibilitychange is the standard hook for pausing expensive work (video, polling, animation) exactly then, and resuming it the moment the tab becomes visible again, rather than burning battery and bandwidth on a tab nobody's looking at.

BroadcastChannel — talking to your own other tabs

const channel = new BroadcastChannel("cart-updates");

channel.postMessage({ itemCount: 3 });                 // every OTHER tab/window on the same origin hears this

channel.addEventListener("message", (event) => {
  console.log("another tab said:", event.data);
});

Same-origin tabs can already coordinate through the storage event (which fires when another tab writes to localStorage), but that only works as a side effect of actually storing something. BroadcastChannel is built for exactly this job directly — send a message to every other open tab of the same site, with no storage write required at all. Closing every tab connected to a channel is the only cleanup needed; there's no server, no polling, just same-origin tabs talking directly.

Web Share and Geolocation, briefly

await navigator.share({ title: "Check this out", url: location.href });   // hands off to the OS's native share sheet

navigator.geolocation.getCurrentPosition(
  (pos) => console.log(pos.coords.latitude, pos.coords.longitude),
  (err) => console.log("denied or unavailable:", err.message)
);

Both need a real user gesture and a secure context, and both are permission-gated the same way clipboard reads are. navigator.share only exists where the OS has a native share sheet to hand off to — always feature-detect (if (navigator.share)) and fall back to your own share buttons where it's missing.

Interview answer, one sentence

"The browser hands you storage, URLs, observers, the clipboard, cross-tab messaging and visibility as APIs, and the skill is picking the smallest one that fits — URLSearchParams over string slicing, IntersectionObserver over scroll listeners, delegation over a listener per element — while remembering many are asynchronous or permission-gated."

Before you move on
  • Explain why editing url.searchParams and calling url.toString() reflects the change immediately, with no manual string concatenation.
  • Predict the click log order in the bubbling demo with the capture checkbox on versus off, and say why capture-phase listeners fire first.
  • Explain what event.stopPropagation() actually stops, and why event delegation depends on propagation still working elsewhere.
  • Explain why visibilitychange, not a periodic check, is the right hook for pausing video or polling when a tab isn't visible.
  • Name two APIs from this chapter that require both a secure context and a real user gesture, and explain why that restriction exists.

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 10 testsRead one query parameter from a URLintermediate
  • 11 testsBuild a query string from an objectintermediate
All 33 for this level →
I10

Finished reading?

0 of 41 chapters read in this series

→Next stop

  1. I9Previous · I9Offline & storage
  2. I10you are here
  3. Next · I11Modern JavaScript, ES2023 to ES2026What the language gained in the last few years, and how to tell whether you can use it yet.7 min read

01On this page

9 sections · 0%

  1. 01Browser storage
  2. 02URL and URLSearchParams
  3. 03History and IntersectionObserver, briefly
  4. 04Events, in depth — live
  5. 05Delegation and custom events
  6. 06Clipboard — reading and writing outside the page
  7. 07Page Visibility — knowing when nobody's looking
  8. 08BroadcastChannel — talking to your own other tabs
  9. 09Web Share and Geolocation, briefly

[] chapters · t top