Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

27 chapters
⌕
Beginner9›
B1Setup & mental modelB2Types & valuesB3Operators & flowB4Functions (first half)B5Objects & arrays (first half)B6DOM & eventsB7Basic asyncB8Errors & tools★Cheat page
Intermediate10›
I1Scope & functions, properlyI2Objects deeplyI3Prototypes & OOPI4Async, properlyI5Modules & toolingI6Regex, dates & APIsI7Error handlingI8Real-time connectionsI9Offline & storage★Cheat page
Advanced10›
A1Engine & memoryA2Advanced asyncA3MetaprogrammingA4Types & dataA5Patterns & architectureA6PerformanceA7SecurityA8EcosystemA9Testing★Cheat page
/ search[ ] chaptert top

JavaScript levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
I6

Regex, dates & browser APIs

Four unrelated toolboxes every real app ends up reaching for.

Regex — the essentials

/abc/         // literal — matches "abc" exactly
/abc/i        // flag: i = case-insensitive
/abc/g        // flag: g = find ALL matches, not just the first
/(\w+)@(\w+)/  // ( ) = a capturing group — grabbed separately from the full match
const text = "contact: ana@example.com today";
const match = text.match(/(\w+)@(\w+)\.com/);
console.log(match[0]);   // the whole match — what happens?
console.log(match[1]);   // group 1 — what happens?
console.log(match[2]);   // group 2 — what happens?

"ana@example.com", then "ana", then "example" — the full match is always index 0, and every parenthesized group after it fills in one more slot, in order.

// Named groups — same idea, readable by name instead of position
const parsed = "2024-01".match(/(?<year>\d{4})-(?<month>\d{2})/);
parsed.groups.year;    // "2024"
parsed.groups.month;   // "01"

"2024-01-15".replace(/(\d+)-(\d+)-(\d+)/, "$3/$2/$1");   // "15/01/2024" — $1/$2/$3 refer back to the groups

[..."a1 b22 c333".matchAll(/[a-z](\d+)/g)].map((m) => m[1]);  // ["1", "22", "333"] — every match, not just the first
⚠ A /g regex remembers where it left off .test() and .exec() on a regex literal with the g flag mutate the regex object's own lastIndex — the next call resumes searching from there, not from the start of the string.
const stateful = /\d/g;
console.log(stateful.test("a1"));   // what happens?
console.log(stateful.test("a1"));   // SAME regex, same string — what happens?
console.log(stateful.test("a1"));   // what happens?

true, false, true — alternating, on the exact same input. First call finds the digit and leaves lastIndex at 2; second call starts searching from index 2 in a 2-character string, finds nothing, and resets lastIndex back to 0; third call starts over and finds it again. Reusing one global-flagged regex object across unrelated calls is exactly how this bites — a fresh /\d/g literal each time, or dropping the g flag for a one-shot .test(), avoids it.

Dates

const d = new Date(2024, 0, 15);   // year, MONTH (0-indexed!), day
console.log(d.getMonth());   // what happens?
console.log(d.getDate());    // what happens?

0, then 15 — getMonth() is January-is-0, a decision baked into Date since the original Java date API it was modeled on in 1995, and never fixed since without breaking every existing script.

const start = new Date("2024-01-15");
const end = new Date("2024-02-15");
(end - start) / 86_400_000;   // 31 — subtracting Dates gives milliseconds; divide to get days
⚠ Why almost nobody hand-rolls date math Time zones, daylight saving transitions, leap years, and leap seconds all make "add one day" genuinely harder than + 86400000 — a DST boundary can make that arithmetic land on the wrong calendar day entirely. This is the real reason libraries like date-fns/Temporal (the successor API, still stabilizing) exist: not laziness, a correctness problem that's easy to get subtly wrong by hand.
new Intl.DateTimeFormat("en-IN", { dateStyle: "long" }).format(d);
// "15 January 2024" — locale-correct formatting, no manual string building

new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(150000);
// "₹1,50,000.00" — Indian digit grouping, handled for you

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.

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.

Practice this layer

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

Pull every hashtag out of a post3 tests · intermediateRead one query parameter from a URL3 tests · intermediate
←previousModules & tooling↑ CovernextError handling→