Selecting and Reading
Selecting and Reading
document.querySelector accepts full CSS selectors and returns the first match; querySelectorAll returns a static NodeList snapshot, while element.children-style accessors like getElementsByClassName return live collections that mutate as the DOM changes — a classic source of skipped-item bugs when iterating while removing.
Reading textContent, classList, and dataset is cheap; prefer textContent over innerHTML for text you did not author, since assigning to innerHTML parses markup and becomes an XSS sink for any string that originated from a user or API. For attributes, getAttribute/setProperty? Use el.dataset.count = '3' to bind data-* values with automatic camelCase mapping (dataset.count ↔ data-count).
Creating, Mutating, Listening
Creating, Mutating, Listening
const li = document.createElement('li');
li.textContent = item.text;
li.addEventListener('click', onSelect);
list.append(li); // append/prepend/add before
li.remove(); // detach
Events bubble: a click on a nested span climbs span → li → ul → document. That enables delegation — one listener on the container using e.target.closest('li') handles items that do not exist yet, which is both less memory and correct for re-rendered lists. Listener options: { once: true } auto-removes after firing, { capture: true } listens on the way down, { passive: true } promises not to preventDefault() (faster scrolling), and { signal } ties removal to an AbortController.
Pitfalls and Interview Focus
Pitfalls and Interview Focus
stopPropagation cuts bubbling — overusing it couples components that never heard of each other; prefer delegation and scoped handlers. preventDefault exists for default browser behavior (form submit, link navigation), not for stopping propagation — they are different questions interviewers love to split.
Performance: reading layout properties (offsetHeight, getBoundingClientRect) forces a synchronous reflow; interleave reads before writes or batch mutations with a DocumentFragment so one reflow serves many inserts. Memory: detached nodes stay alive if a closure or listener still references them — remove listeners (AbortController) when tearing down long-lived UI.
Interview: explain bubbling in one sentence, then implement delegation for a dynamically loaded table.
Quiz
1. What's the difference between querySelectorAll and getElementsByClassName?
2. Why use event delegation on the list container?
3. Which call forces a synchronous reflow?
Flashcards
Question
Static vs live collection
Click to reveal answer
Answer
querySelectorAll = snapshot; getElementsBy* = live view that mutates with the DOM.
Question
Event bubbling
Click to reveal answer
Answer
Click on a child climbs ancestors; delegation exploits it with e.target.closest().
Question
preventDefault vs stopPropagation
Click to reveal answer
Answer
Stops browser default behavior vs stops the event traveling up the tree.
Question
passive listener
Click to reveal answer
Answer
Handler guarantees no preventDefault — lets the browser scroll without waiting.
Question
innerHTML danger
Click to reveal answer
Answer
Parses markup → XSS with untrusted strings; use textContent for plain text.
Revision Notes
Key Takeaways
- 1. Prefer querySelector + textContent; treat innerHTML as an injection risk
- 2. Delegate clicks on containers — robust for dynamic content, cheaper in memory
- 3. Read layout in batches; separate reads from writes to avoid reflow storms
Interview Tips
- • Describe bubbling precisely, then show delegation with closest() in real code
- • Distinguish preventDefault from stopPropagation — a standard screening split
Cheat Sheet
querySelector(All) static vs getElementsBy* live. textContent over innerHTML (XSS). Delegate with bubbling + e.target.closest(). Options: once/capture/passive/signal. Batch reads before writes.