JavaScript DOM Collections
When you query the DOM for multiple elements, you get back a collection: either an HTMLCollection or a NodeList. Understanding the differences between these two collection types is important for correctly working with DOM elements.
HTMLCollection vs NodeList
HTMLCollection and NodeList are both array-like objects that hold DOM elements, but they have key differences in how they behave and what they contain.
| Feature | HTMLCollection | NodeList |
|---|---|---|
| Contains | Elements only | Any node type |
| Live/Static | Always live | Usually static (querySelectorAll) |
| Access by name | Yes (.namedItem()) | No |
| forEach() | No (must convert) | Yes |
| Returned by | getElementsBy* | querySelectorAll, childNodes |
// HTMLCollection (live)
// const byTag = document.getElementsByTagName('p');
// const byClass = document.getElementsByClassName('item');
// NodeList (static)
// const byQuery = document.querySelectorAll('p');
// const childList = element.childNodes;
// Key difference: live vs static
console.log('HTMLCollection is LIVE:');
console.log(' - Automatically updates when DOM changes');
console.log(' - Length changes if elements are added/removed');
console.log('\nNodeList from querySelectorAll is STATIC:');
console.log(' - Snapshot at time of query');
console.log(' - Does NOT update when DOM changes');
console.log('\nNodeList from childNodes is LIVE');
console.log(' - Updates when children change');Live vs Static Collections
A live collection automatically reflects changes to the DOM. A static collection is a snapshot taken at the time of the query and does not change when the DOM is modified.
// Live collection example
// const items = document.getElementsByClassName('item');
// console.log(items.length); // e.g., 3
// // Add a new element with class 'item'
// document.body.innerHTML += '<div class="item">New</div>';
// console.log(items.length); // 4 - automatically updated!
// Static collection example
// const items = document.querySelectorAll('.item');
// console.log(items.length); // 3
// document.body.innerHTML += '<div class="item">New</div>';
// console.log(items.length); // Still 3 - snapshot!
// Simulating the difference
let liveCollection = [1, 2, 3];
const staticCollection = [1, 2, 3]; // Snapshot
liveCollection.push(4);
console.log('Live after adding: ' + liveCollection);
console.log('Static (unchanged): ' + staticCollection);
console.log('\nBe careful with live collections in loops!');
console.log('Adding/removing elements can cause infinite loops');Converting to Array
Both HTMLCollection and NodeList are array-like objects but are not true arrays. To use array methods like map, filter, and reduce, you need to convert them to arrays.
// Method 1: Array.from()
// const arr1 = Array.from(document.querySelectorAll('p'));
// Method 2: Spread operator
// const arr2 = [...document.querySelectorAll('p')];
// Method 3: Array.prototype.slice.call()
// const arr3 = Array.prototype.slice.call(collection);
// Simulating conversion
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
// Convert to real array
const arr = Array.from(arrayLike);
console.log('Is array: ' + Array.isArray(arr));
console.log('Array: ' + arr);
// Now you can use array methods
const upper = arr.map(function(item) {
return item.toUpperCase();
});
console.log('Mapped: ' + upper);
const filtered = arr.filter(function(item) {
return item !== 'b';
});
console.log('Filtered: ' + filtered);Iterating Collections
NodeList supports forEach() directly. HTMLCollection does not. Both support for...of loops and index-based access.
// NodeList - supports forEach
// document.querySelectorAll('p').forEach(function(p, i) {
// console.log(i + ': ' + p.textContent);
// });
// HTMLCollection - no forEach, use for loop or convert
// const items = document.getElementsByTagName('li');
// for (let i = 0; i < items.length; i++) {
// console.log(items[i].textContent);
// }
// for...of works with both
// for (const item of items) {
// console.log(item.textContent);
// }
const items = ['Apple', 'Banana', 'Cherry'];
// forEach (NodeList)
console.log('forEach:');
items.forEach(function(item, i) {
console.log(' ' + i + ': ' + item);
});
// for...of (both)
console.log('for...of:');
for (const item of items) {
console.log(' ' + item);
}Which Methods Return What
Knowing which methods return HTMLCollection and which return NodeList helps you predict the collection's behavior.
// Returns HTMLCollection (LIVE):
console.log('HTMLCollection (live):');
console.log(' document.getElementsByTagName("p")');
console.log(' document.getElementsByClassName("cls")');
console.log(' element.children');
console.log(' document.forms');
console.log(' document.images');
// Returns NodeList (STATIC from querySelectorAll):
console.log('\nNodeList (static):');
console.log(' document.querySelectorAll("p")');
// Returns NodeList (LIVE):
console.log('\nNodeList (live):');
console.log(' element.childNodes');
// Returns single Element:
console.log('\nSingle Element:');
console.log(' document.getElementById("id")');
console.log(' document.querySelector(".class")');