Step 02

Selection

Nearly everything you will ever do with the DOM is one move, repeated:

Select the node(s) of interest, manipulate them, and repeat, until the page is in its new state.

This step is the first half of that move. The API has accumulated selection methods for thirty years, so we climb them as a ladder — oldest to newest — and end on the two you will actually reach for.

Learning Objectives

  • Recognize legacy named access (document.forms and friends) in older code
  • Select by id with getElementById, the long-standing workhorse
  • Distinguish a live HTMLCollection from a static NodeList
  • Use any CSS selector via querySelector / querySelectorAll, scoped to any element

The method ladder

Legacy named access

document.forms.length; // every <form> in the document document.forms['form2']; // access by name attribute

The oldest layer: the document exposes collections like forms and images, indexable by name. It still works, and you should recognize it in code you inherit — but do not build on it. Names collide, and the collections cover only a few element types.

getElementById

const el = document.getElementById('p1');

The workhorse since DOM Level 1, and still the fastest, most direct call when you have named a thing with an id. One element or null — no collection to unwrap.

getElementsBy* — and the live-collection trap

const els = document.getElementsByClassName('fancy'); // HTMLCollection — live

These return an HTMLCollection, and it is live: it is a view of the tree, not a copy. Add a matching element and the collection you are holding grows; remove elements while looping over the collection and items shift under your index. That behavior has produced decades of off-by-one loop bugs.

querySelector / querySelectorAll

const first = document.querySelector('.fancy'); // first match or null const all = document.querySelectorAll('p > strong'); // NodeList — static const scope = document.getElementById('p3'); scope.querySelectorAll('strong'); // scoped: only inside #p3

The modern pair takes any CSS selector — the same language you already use in stylesheets. Learn selectors once, use them everywhere: that is the payoff of the whole select-and-effect design. querySelectorAll returns a static NodeList, a snapshot that holds still no matter what happens to the tree afterward. And both methods run on any element, not just document, so you can scope a query to the subtree you care about instead of searching the whole page.

Default to querySelector / querySelectorAll. getElementById is still fine — it is direct and fast — but the query pair covers every case with one selector language, and the static list it returns behaves the way you expect a list to behave.

Live demo

One shared sample DOM — three forms, three paragraphs, some nested <strong> elements — and every rung of the ladder run against it. Whatever a button selects flashes highlighted for two seconds, so you can see the set each method returns, not just its length. Finish in section 5, the selector playground: type any CSS selector, press Enter, and see what matches — including what happens when the selector is invalid (the call throws, and the demo catches it).

Next Steps

Querying is not the only way to reach a node. In Step 3: Walking the tree, we move by relationship instead of by selector:

  • Walk with children, parentNode, and the sibling properties
  • See why every relationship comes in a node flavor and an element flavor
  • Walk upward with closest()
  • Traverse an entire subtree depth-first