Was reading this article off of the FreeCodeCamp.org blog. Realized that while I pretty interchangeably use document.querySelector
[All](___)
and document.getElement
[s]By[
Something](___)
, though I’ve come to prefer the former, there is a subtle difference in how you mutate the DOM elements you’ve selected. They both produce Array-like (“Array-like”? Thanks JavaScript🙄🤐) collections, but whereas the ‘querySelector’ options return a DOM-_NodeList_, the ‘getElement’ versions return an HTMLCollection. These two entities are remarkably similar, but are distinct.
Get ‘Em: NodeList vs HTMLCollection
Say I have the following HTML in my page:
<div class="getMe" id="gIndx0"> You are cool. </div>
<div class="queryMe" id="qIndx0"> You are neat. </div>
<div class="getMe" id="gIndx1"> I like you. </div>
<div class="queryMe" id="qIndx1"> I lurvz you. </div>
In order to get at all of the div
s with class foo
, I could go one of two ways:
const getByVersion = document.getElementsByClassName('getMe'); // ⟹ returns an "HTMLCollection"
const queryVersion = document.querySelectorAll('.queryMe'); // ⟹ return a "NodeList"
console.log(getByVersion.length); // ⟹ 2
console.log(queryVersion.length); // ⟹ 2
console.log(Array.isArray(getByVersion)); // ⟹ false !
console.log(Array.isArray(queryVersion)); // ⟹ false !
Change ‘Em
Let’s see some ways of manipulating our selections by doing something like, idunno, changing the innerText to “Rich Rules”. Both NodeList
s and HTMLCollection
s can be interated over to make changes to each selection.
1. Use Object.entries(___)
to cast to an Array
We can use the ES6 Object.entries()
method to make either type of collection an Array, and then we can use any of our normal Array methods, including .forEach(x=>{})
or .map(x=>{});
. Either method will work with either the HTMLCollection or NodeList (even if map
is technically returning a new array and forEach
isn’t).
( Object.entries()
returns an Array of two-element sub-Arrays in the form [
indxNumber,
value ]
, so you’ll note that I have to use a [1]
in order to access the <div>
s. )
/* Either of these are interchangeable for both types of colletions */
Object.entries(getByVersion).map((elem) => {
elem[1].innerText = 'Rich Rules';
});
Object.entries(queryVersion).forEach((elem) => {
elem[1].innerText = 'Rich Rules';
}); // same effect
Thing is, I don’t like this approach, because I don’t conceptualize the selected DOM elements as “Objects” really, any more than “everything in JavaScript is an object”.
2. Use Array.from(___)
to cast to an Array
I like it more, because, well, I’m making an Array from the DOM collections.
Array.from(getByVersion).map((elem) => {
elem.innerText = 'Rich Rocks';
});
3. MY RECOMMENDED Approach - The ES6 [...
spread]
syntax
The beauty of the ES6 ...
“Spread-Operator” is that it saves your characters. Works pretty much exactly like Array.from()
but is more concise!
[...getByVersion].map((elem) => {
elem.innerText = 'Rich Reads';
});
[...queryVersion].forEach((elem) => {
elem.innerText = 'Rich Reads';
});
4. Oldskool/Pre-ES6 = Use call
with Array.prototype
methods
To use array methods on Array-like objects, you have to call the methods like this:
Array.prototype.
array_method_name.call(
array-like_object,
function);
So, to case to an Array, something like, Array.prototype.slice.call(queryVersion)
.
Suffice it to say, anytime you are using call
or apply
things can get very messy very quickly.
An important thing to remember is that you can’t ever be sure that the collection you’re looking at is actually live and has the most up-to-date DOM state. Since browser implementations vary, you should always “refresh” your selection to make sure you are using the latest and greatest.
Case in point: Let’s add new DIVs to the DOM via JavaScript…
function makeANewDivFunc(nameForClass) {
const newDiv = document.createElement('div');
newDiv.classList.add(nameForClass, 'addedLater');
newDiv.innerText = 'I have a class ' + nameForClass;
return newDiv;
}
document.body.appendChild(makeANewDivFunc('getMe'));
document.body.appendChild(makeANewDivFunc('queryMe'));
/* AFTER CHANGES: */
console.log(getByVersion.length); // 3 ⤆ UPDATED [in Chrome]
console.log(queryVersion.length); // 2 ⤆ NOT Updated / No longer live!
NodeList vs HTMLCollection - What’s the difference?
A NodeList
can contain any type of node, but everything in the DOM is a node object. An HTMLCollection
is only going to contain a type of node that is an “Element”. An element could be some <li>
, <div>
, the <body>
, or even the window itself [don’t touch that!].
The main functional difference has to do with being an “Iterable” array-like collection. I’m not going to get into the ES6+ notion of iterability or whether or not things have a .next()
property, because that is its own subject, but I’m going to boil things down to this:
The best benefit to using document.querySelectorAll('.someClass')
is that you can go right to a forEach()
without having to cast to an actual Array first.