My Profile Photo

Rich Werden

Web Developer & Software Engineer


A site for Rich to write about code and show some completed projects for future reference...


Querying, Selecting, & Mutating DOM Elements

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 divs 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 NodeLists and HTMLCollections 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';
});

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.