When you query the browser for DOM elements, there are some footguns you should know about.
Some lists are live
Some DOM APIs return live lists that automagically update their contents as the underlying DOM is manipulated.
Example
Let's assume we have two <div> elements:
<div id="one"></div>
<div id="two"></div>
We have multiple ways to retrieve a list of these elements. At first glance, they all look the same:
let liveList = element.getElementsByTagName('div')
let nonLiveList = document.querySelectorAll('div')
console.log(liveList) // [#one, #two]
console.log(nonLiveList) // [#one, #two]
However, when we remove one element from the DOM, the live list reflects the change automatically:
document.querySelector('#two').remove()
console.log(liveList) // [#one]
console.log(nonLiveList) // [#one, #two]
Snapshotting a live list
Lists that silently change their elements can be very surprising to work with.
If you cannot find a non-live version of that method, you can make a snapshot by copying it to an array:
let childrenSnapshot = [...element.children]
Returned lists are not arrays
When a DOM API returns a list, it is either a NodeList or an HTMLCollection. Both are
array-like
Show archive.org snapshot
objects that only support basic iteration. However, only true arrays support the full range of methods like push(), map() or slice():
let htmlCollection = element.getElementsByTagName('div')
let nodeList = document.querySelectorAll('div')
for (let e of htmlCollection) { } // ✔ works
for (let e of nodeList) { } // ✔ works
htmlCollection.map(...) // ✘ error
nodeList.map(...) // ✘ error
Using Array methods on DOM lists
If you need to use an unsupported array method, you have three choices.
You can copy the DOM list into an array:
let array = [...nodeList]
array.map(...) // ✔ works
You can apply an Array method to the array-like object:
Array.prototype.map.call(nodeList, ...) // ✔ works
Or you might already use a utility package that works on array-like objects:
up.util.map(nodeList, ...) // ✔ works
Some lists contain text nodes
Some API methods return lists that not only contain elements, but also text nodes or comment nodes.
For example, we have a <div> with a single child element:
<div id="container">
<span>hello</span>
</div>
When we query that element for its children, some properties include text nodes:
container.children // => [span]
container.childNodes // => [text, span, text]
In this example, the text nodes are the whitespace before and after the <span>.
When you want to exclude text nodes, you will usually find a different method in the API. E.g. element.nextSibling wg. element.nextElementSibling.
Most notably, querySelectorAll() will only select elements.