Selecting Elements

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.

Element selection methods
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:

Live HTMLCollection versus static NodeListHTMLLive
<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>
Browser output of Listing 8.16
Browser output of 16

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).