Live Element Lists vs Static Ones, Why document.getElementsByClassName is different to document.querySelector(‘.className’) : Bitesize JS

Bitesize Javascript series

I was working on a bug in our code base with a colleague, when he noticed something strange about the behaviour of document.getElementsByClassName.

In our code base it had been implemented as if it acted like document.querySelector, ie. you could save a list of elements to a variable, then mess with those elements parent, but the variable will still contain all those elements as they were when you set it.

When you call document.getElementsByClassName a HTMLCollection is returned. This list is a LIVE list*.

When you call document.querySelector a NodeList is returned. This is NOT a live list but is instead a STATIC list* (ie. it should not change).

const a = document.querySelectorAll(‘.list-of-child-elements’)
a
//Displays:
NodeList[div.list-of-child-elements]
const b = document.getElementsByClassName(‘list-of-child-elements’)
b
//Displays:
HTMLCollection[div.list-of-child-elements]


At this point the two lists look pretty similar.

Now let us select the parent element to the child elements with the class name ‘list-of-child-elements’.

const x = document.getElementsByID('parent-wrapper')
x
//Displays:
<div id="parent-wrapper"> ... </div>


Ok so lets do something to this parent element, this is the element which contains that child elements.

x.appendChild(a[0].cloneNode(true))
//Displays:
<div class="list-of-child-elements">...</div>


Here we have cloned one of the child elements, by getting the first element in the list of child elements and cloning it with .cloneNode(true). Then we have appended this as a child to the parent element.

If we look in the HTML at this point there will be a new div under the parent element ‘parent-wrapper’ exactly the same as one of it’s children ‘list-of-child-elements’.

Now when we go into the console we see the difference between the static and live lists:

a
//Displays:
NodeList[div.list-of-child-elements]
b
//Displays:
HTMLCollection(2)[div.list-of-child-elements, div.list-of-child-elements]


The list which was created by .getElementsByClassName has been updated so that it is correct as of the new DOM model. The list which was created by .querySelectorAll is the same as when it was first set to the variable, so when the DOM was originally used to query the html.

*N.B. Neither of these are arrays of elements but instead html collections or node lists. While they may look like elements, to actually make them arrays you need to call Array.prototype.slice.call(collection/nodeList), on them to make them a proper array**.

**Javascript technically does not have arrays but instead objects which behave almost identically to arrays.

Leave a Reply

Your email address will not be published. Required fields are marked *