JS DOM Collections
In this page:
NodeList from querySelectorAll
document.querySelectorAll() returns a NodeList -- a static snapshot of matching elements at the moment the query ran, which does not automatically update if the DOM changes afterward, and which directly supports the forEach() method for iteration.
Note: Use querySelectorAll() as your default way to select multiple elements, since its resulting NodeList's forEach() support makes iteration straightforward.
Warning: A NodeList's static nature means it will not reflect new elements added to the page after the query ran -- re-run the query if you need an up-to-date collection.
Example: NodeList from querySelectorAll
<p class="item">A</p><p class="item">B</p>
<script>
const list = document.querySelectorAll(".item"); // static NodeList
list.forEach(el => console.log(el.textContent));
</script>
HTMLCollection from getElementsByClassName/TagName
document.getElementsByClassName() and getElementsByTagName() return an HTMLCollection -- a "live" collection that automatically updates to reflect DOM changes as they happen, but which does not support forEach() directly, requiring conversion to an array first for most iteration needs.
Note: Convert an HTMLCollection to a real array with Array.from() before using array methods like forEach, map, or filter on it.
Warning: Looping over a live HTMLCollection with a plain for loop while simultaneously adding or removing matching elements can produce confusing results, since the collection's length changes mid-loop.
Example: HTMLCollection from getElementsByClassName/TagName
<p class="item">A</p>
<script>
const collection = document.getElementsByClassName("item"); // live HTMLCollection
console.log(collection.length);
</script>
Converting Collections to Real Arrays
Array.from(collection) (or the spread syntax [...collection]) converts either a NodeList or an HTMLCollection into a genuine JavaScript array, unlocking the full array method set -- map, filter, reduce, and more -- that neither DOM collection type fully supports on its own.
Note: Convert to a real array with Array.from() the moment you need anything beyond basic iteration, like transforming or filtering the collection.
Warning: Converting a live HTMLCollection to an array captures a fixed snapshot at that moment -- the resulting array will not continue to update the way the original live collection would have.
Example: Converting Collections to Real Arrays
<p class="item">A</p><p class="item">B</p>
<script>
const collection = document.getElementsByClassName("item");
const arr = Array.from(collection);
console.log(arr.map(el => el.textContent));
</script>
Choosing the Right Selection Method
querySelectorAll() with its flexible CSS-selector syntax and forEach-ready NodeList is the more modern, generally preferred default; getElementsByClassName/TagName remain useful specifically when you want a live, auto-updating collection that tracks DOM changes automatically.
Note: Default to querySelectorAll() for most element selection needs; reach for getElementsBy* specifically when you deliberately want a live collection.
Warning: Choosing a live HTMLCollection when you actually wanted a fixed snapshot (or vice versa) can introduce subtle bugs that only appear once the DOM changes after the initial selection.
Example: Choosing the Right Selection Method
<p class="item">A</p>
<script>
console.log(document.querySelectorAll(".item")); // modern default
console.log(document.getElementsByClassName("item")); // live collection
</script>
Common Collection Pitfalls
Two frequent mistakes: forgetting a collection might be empty (checking .length before assuming an element exists), and modifying the DOM while iterating a live HTMLCollection with a plain for loop, which can cause elements to be skipped or visited twice as the collection's length shifts mid-loop.
Note: When removing elements from a live collection while iterating it, loop backward (from the end toward the start) or convert to a static array first, to avoid the shifting-length problem.
Warning: Removing elements from a live HTMLCollection while iterating it forward with a standard for loop is a classic bug -- indices shift as elements are removed, causing some to be skipped.
Example: Common Collection Pitfalls
<p class="item">A</p>
<script>
const items = document.querySelectorAll(".missing");
if (items.length === 0) console.log("No matching elements found"); // check length first
</script>
- Assuming every DOM collection supports array methods like .map() or .filter() directly -- a NodeList supports forEach, but an HTMLCollection does not support even that without converting it first.
- Not realizing that an HTMLCollection is "live" (automatically reflecting DOM changes), while a NodeList from querySelectorAll is "static" (a fixed snapshot) -- looping over a live collection while modifying the DOM can behave unexpectedly.
- Forgetting to convert a collection to a real array (with Array.from() or the spread operator) before using array methods that neither collection type supports natively.
- querySelectorAll() returns a static NodeList, which supports forEach() but not other array methods directly.
- getElementsByClassName() and getElementsByTagName() return a live HTMLCollection, which does not support forEach() at all.
- Array.from(collection) converts any DOM collection into a real array, unlocking the full set of array methods.
NodeList and HTMLCollection have both been supported in every browser since the earliest DOM implementations.
Chapter Quiz — Complete all 26 topics to unlock
0/26 topics done
Complete these topics first:
- JS Dates
- JS Math
- JS Conditionals
- JS Switch
- JS Loop For
- JS Loop While
- JS Iterables
- JS Sets
- JS Maps
- JS typeof
- JS Type Conversion
- JS Destructuring
- JS Arrow Functions
- JS Classes
- JS Modules
- JS Promises
- JS Async/Await
- JS DOM
- JS DOM Methods
- JS Events Advanced
- JS DOM Navigation
- JS DOM Collections
- JS Async Callbacks
- JS Async Parallel
- JS Date Set
- JS Set Logic