You can find elements by id, class, tag, name or any CSS selector (Selectors); getElementById() and getElementsByName() exist only on document. A live collection re-queries the tree whenever you read it; a static one is a snapshot.
| Method | Returns | Live? |
|---|---|---|
| getElementById(id) | Element or null | n/a |
| querySelector(sel) | First match or null | n/a |
| querySelectorAll(sel) | NodeList | Static |
| getElementsByClassName() | HTMLCollection | Live |
| getElementsByName(name) | NodeList | Live |
Removing a class while looping over a live collection shrinks it mid-loop and skips items:
<pre id="out" style="font-size: 15px; margin: 0"></pre>
<ul id="todo" style="display: flex; gap: 32px">
<li class="open">Test</li><li class="open">Fix</li><li class="open">Ship</li>
</ul>
<script>
const todo = document.querySelector('#todo');
const live = todo.getElementsByClassName('open'); // HTMLCollection
const snapshot = todo.querySelectorAll('.open'); // static NodeList
for (const li of live) li.classList.remove('open'); // bug: skips items
const out = [`after live loop: ${live.length} still open`];
for (const li of snapshot) li.classList.remove('open'); // safe
out.push(`after snapshot loop: live=${live.length} snapshot=${snapshot.length}`);
document.querySelector('#out').textContent = out.join('\n');
</script>
closest(sel) tests the element, then its ancestors: event.target.closest('button') finds a button even when the click hit an icon inside it (Delegation and Batching).