querySelector vs getElementsByClassName (NodeList vs HTMLCollection)
Tags: webdev, beginners, html
Published: 2020-09-25 03:58:31


For this blog entry, I wanted to talk about DOM traversal, but quickly got derailed by some funky JavaScript document methods and their idiosyncrasies. However, let’s start at the beginning: 

The DOM or Document Object Model, which is the HTML skeleton forming the body of every webpage, is a tree. Any time we call JavaScript’s built-in querySelector method on the document or on a particular node, we are traversing through that tree and inspecting the CSS selectors of elements within that tree.

`COMPUTER SCIENCE ALERT`
*Trees are a common data structure in computer science and search trees are a common type of tree data structure used for finding keys in a set.*

MDN describes querySelector as implementing:
> “depth-first pre-order traversal of the document's nodes starting with the first element in the document's markup and iterating through sequential nodes by order of the number of child nodes.” 

While at present I haven’t found confirmation that other document methods use this same depth-first pre-order traversal strategy to navigate the DOM tree, I did stumble upon some important differences between these document methods that I’d like to cover. The issue is, what are the return values of these different document methods?

* querySelector() returns an element. More specifically, MDN explains that its return value is: 
> “An HTMLElement object representing the first element in the document that matches the specified set of CSS selectors, or null is returned if there are no matches.” 

* Seems simple enough. However, querySelectorAll() may have to return more than one element, so how does it do this? It returns a *static* NodeList representing all the matching HTML elements. Well what does it mean that it’s static? We’ll get to that shortly.

* getElementById() also returns an element. However, MDN simply calls this an “Element object describing the DOM element object matching the specified ID.” It does not call it an HTMLElement as it does the return value of querySelector, which leads me to believe that there is a difference here. However, at least  it is still a single return value.

* getElementsByClassName() returns a *live* HTMLCollection, “an array-like object of all child elements which have all of the given class name(s)” (MDN). 

###What is practically important about this?
* querySelectorAll returns a NodeList (static)
* getElementsBy___ returns an HTMLCollection (live)

NodeLists returned by querySelectorAll methods are static, but depending on how we manipulate them, their return values can either stay static or become live. Node.childNodes is live. 

Neither of these are arrays, they are just different types of objects. HOWEVER, with NodeLists (which are newer/more modern) you can still use forEach() to iterate through them.

You cannot directly use forEach to iterate through an HTMLCollection. (If you need to though, you can use the Array.from() method to turn an HTMLCollection into an array, and then you can iterate through it using .forEach(), .map, etc.)

The difference between static and live is that live reflects exactly what is currently on the DOM, whereas static collections will not update if something is deleted on the DOM. 

A helpful thing to remember is that some dude on the internet (sorry dude, I forget your name but I appreciate you) said that HTMLCollections are an artifact we just have to deal with. I think this preference for NodeLists is also related to the HTMLCollection annoyance of not being able to directly iterate through them. 

* Tl;dr: probably just use querySelector() or querySelectorAll() to avoid issues with HTMLCollections. 
* Further reading: what is meant on MDN by differentiating between an Element and an HTMLElement?