DOM Manipulation
Select, create, and modify DOM elements and handle user events for interactive web pages.
Selecting Elements Efficiently
Modern APIs favor `document.querySelector` and `querySelectorAll` for flexible CSS-based selection. `getElementById` remains the fastest for ID lookups. Cache selections when reused inside loops or frequent handlers.
In framework-driven apps, direct DOM manipulation is less common, but understanding selection is essential for legacy code, Web Components, browser extensions, and testing utilities that interact with the page outside React/Vue render cycles.
- `querySelector` for flexible CSS selectors
- Cache repeated lookups
- Scope queries with element.querySelector
const form = document.querySelector('#signup-form');
const inputs = form.querySelectorAll('input[required]');Creating and Updating Elements
Use `document.createElement` and DOM APIs like `append`, `prepend`, and `replaceWith` for safe structural changes. Prefer `textContent` over `innerHTML` when inserting user-provided text to prevent XSS.
When HTML strings are required, sanitize with a trusted library or use `template` elements with cloned content. Batch DOM writes by building fragments offline before attaching to the document to minimize reflows.
- `createElement` + append for safe inserts
- `textContent` avoids XSS vs `innerHTML`
- DocumentFragment for batch inserts
const li = document.createElement('li');
li.textContent = item.label;
list.append(li);Event Listeners and Delegation
Add listeners with `addEventListener(type, handler, options)`. The `passive: true` option improves scroll performance for touch and wheel events. Remove listeners with `removeEventListener` when elements are destroyed to prevent leaks.
Event delegation attaches one listener on a parent and uses `event.target` matching to handle child interactions. This pattern scales well for dynamic lists and reduces memory compared to per-row listeners.
- Use delegation for dynamic lists
- `passive: true` for scroll/touch
- Remove listeners on teardown
list.addEventListener('click', (e) => {
const row = e.target.closest('[data-id]');
if (row) handleRowClick(row.dataset.id);
});Attributes, Classes, and Dataset
The `classList` API provides `add`, `remove`, `toggle`, and `contains` for class management without string parsing. Data attributes (`data-*`) surface as `element.dataset.camelCase` for storing lightweight configuration on elements.
ARIA attributes belong in the DOM for accessibility. When manipulating visibility, prefer toggling classes or attributes over inline styles so CSS remains the single source of visual truth.
- `classList` for class toggling
- `dataset` for data-* attributes
- Use ARIA attributes for accessibility
button.classList.toggle('is-active');
button.dataset.loading = 'true';Performance and Layout
Reading layout properties (`offsetWidth`, `getBoundingClientRect`) after writing styles forces synchronous layout (layout thrashing). Batch reads then writes, or use `requestAnimationFrame` to align DOM mutations with the browser paint cycle.
For large lists, virtualize rendering instead of mounting thousands of nodes. Intersection Observer lazy-loads content as users scroll, reducing initial DOM size and improving Time to Interactive.
- Avoid interleaved read/write layout calls
- Virtualize long lists
- Intersection Observer for lazy content