Chapter 37: DOM Node List
XML DOM NodeList — written as if I am your patient teacher sitting next to you.
We will go slowly and step by step, with many small-to-medium examples, visual analogies, real code you can copy-paste, common beginner mistakes, important differences from arrays, and practical patterns used in real projects.
1. What is a NodeList exactly?
NodeList is a special collection type in the DOM that holds a list of nodes.
When you use methods like:
- querySelectorAll()
- getElementsByTagName()
- getElementsByClassName()
- childNodes
- attributes
… the result is almost always a NodeList.
Key facts you must remember from day one:
| Property / Fact | Explanation | Example value / behavior |
|---|---|---|
| Is it an array? | No — it is not a real JavaScript array | Array.isArray(nodeList) → false |
| Can you use .push(), .pop()? | No — NodeList is read-only (you cannot add/remove items directly) | nodeList.push() → TypeError |
| Does it have .length? | Yes — it behaves like an array in this regard | nodeList.length → number of nodes |
| Can you use index access? | Yes — nodeList[0], nodeList[1], etc. | Works like array indexing |
| Is it live or static? | Usually live — changes in the DOM are immediately reflected | Very important! See below |
| Can you use forEach()? | Yes — modern browsers support it natively | nodeList.forEach(node => …) |
| Can you use spread operator? | Yes — …nodeList turns it into real array | const arr = […nodeList]; |
2. Two very important types of NodeList
| Type | Returned by | Is it live? | Meaning / behavior when DOM changes |
|---|---|---|---|
| Live NodeList | childNodes, getElementsByTagName(), getElementsByClassName(), attributes | Yes | If you add/remove elements in the DOM, the NodeList immediately updates |
| Static NodeList | querySelectorAll() | No | Snapshot at the moment you called it — later DOM changes do not affect it |
Live vs Static – very important example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const xml = ` <library> <book id="1">Book A</book> <book id="2">Book B</book> </library> `; const parser = new DOMParser(); const doc = parser.parseFromString(xml, "application/xml"); // Live collection const liveBooks = doc.documentElement.getElementsByTagName("book"); console.log(liveBooks.length); // 2 // Static collection const staticBooks = doc.querySelectorAll("book"); console.log(staticBooks.length); // 2 // Add a new book const newBook = doc.createElement("book"); newBook.textContent = "Book C"; doc.documentElement.appendChild(newBook); console.log(liveBooks.length); // 3 ← updated automatically! console.log(staticBooks.length); // 2 ← still 2, snapshot did not change |
Rule to remember:
If you want the current state of the document → use live collections (getElementsByTagName, childNodes) If you want a stable snapshot → use querySelectorAll
3. How to work with a NodeList in practice
a) Accessing items by index
|
0 1 2 3 4 5 6 7 8 9 |
const books = xmlDoc.querySelectorAll("book"); console.log(books[0]); // first <book> element console.log(books[books.length - 1]); // last <book> |
b) Looping over a NodeList (all modern ways)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 1. Classic for loop (safest & fastest) for (let i = 0; i < books.length; i++) { console.log(books[i].querySelector("title").textContent); } // 2. for...of (clean & modern) for (const book of books) { console.log(book.querySelector("title").textContent); } // 3. forEach (native since ES6) books.forEach((book, index) => { console.log(`Book ${index + 1}: ${book.querySelector("title").textContent}`); }); // 4. Convert to real array (if you need array methods) const bookArray = Array.from(books); bookArray.map(book => book.querySelector("title").textContent); |
c) Checking if NodeList is empty
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
if (books.length === 0) { console.log("No books found"); } if (!books.length) { // same as above } |
Never do this:
|
0 1 2 3 4 5 6 |
if (!books) { ... } // WRONG — NodeList is always truthy, even if empty |
4. Practical real-world example – Working with NodeList from XML
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// Assume xmlDoc is loaded function printBookInfo() { // Get all books (static NodeList) const books = xmlDoc.querySelectorAll("book"); if (books.length === 0) { console.log("No books found in catalog"); return; } console.log(`Found ${books.length} book(s):`); books.forEach((book, index) => { const title = book.querySelector("title")?.textContent || "No title"; const author = book.querySelector("author")?.textContent || "Unknown"; const priceEl = book.querySelector("price"); const price = priceEl?.textContent || "N/A"; const currency = priceEl?.getAttribute("currency") || "Unknown"; console.log(`Book ${index + 1}:`); console.log(` Title: ${title}`); console.log(` Author: ${author}`); console.log(` Price: ${price} {currency}`); console.log(" ────────"); }); // Example: live collection of prices const livePrices = xmlDoc.getElementsByTagName("price"); console.log(`Live price count: ${livePrices.length}`); } |
5. Common mistakes beginners make with NodeList
Mistake 1 — Treating NodeList like array with .push()
|
0 1 2 3 4 5 6 7 |
const books = xmlDoc.querySelectorAll("book"); books.push(newBook); // TypeError: books.push is not a function |
Fix: Convert first
|
0 1 2 3 4 5 6 7 |
const bookArray = Array.from(books); bookArray.push(newBook); |
Mistake 2 — Forgetting NodeList is live
|
0 1 2 3 4 5 6 7 8 9 |
const items = document.getElementsByTagName("item"); for (let item of items) { item.remove(); // infinite loop or unexpected behavior! } |
Fix: Convert to array first or loop backwards
Mistake 3 — Expecting forEach on very old browsers
Fix: Use Array.from() or classic for loop
6. Quick reference – NodeList cheat sheet
| You want to… | Best way / pattern | Live or static? |
|---|---|---|
| Get all books | querySelectorAll(“book”) | static |
| Get all <price> elements | getElementsByTagName(“price”) | live |
| Loop safely | for (let i = 0; i < list.length; i++) or .forEach | — |
| Convert to real array | Array.from(nodeList) or […nodeList] | — |
| Check if empty | nodeList.length === 0 | — |
| Get first / last item | nodeList[0] or nodeList[nodeList.length-1] | — |
| Add array methods (map, filter, etc.) | Array.from(nodeList).map(…) | — |
Would you like to continue with one of these next?
- Live vs static NodeList – detailed experiments
- Converting NodeList → Array – all possible ways & when to use each
- Looping backwards – why & when it saves you from bugs
- NodeList vs HTMLCollection – differences & traps
- Real-world patterns: filtering, mapping, reordering nodes
- Debugging when NodeList behaves “strangely”
Tell me which direction you want to go next! 😊
