querySelectorAll() vs getElementsByClassName

Last modified March 8, 2019
.* :☆゚

querySelectorAll() & getElementsByClassName() are both common JavaScript methods that retrieve elements that match the given selectors. They are both incredibly useful for manipulating elements on the page, but confusion can arise when they are used alongside each other in code, oftentimes with no noticeable difference in the result.

While they can sometimes be used in place of each other, both methods actually return a different kind of output which can greatly affect your code runs. This is especially noticeable when dynamic elements come into play.

You may have also seen `querySelector()` or `getElementsbyID()`- these methods return a **single** element. For the purposes of this article, we'll be talking about lists of elements, however the comparison between `querySelector()` and `getElementsbyID()` is pretty much the same.

For those that are learning the basics of JavaScript, knowing the difference between these two methods can be helpful in allowing you to write cleaner, more meaningful code that stands up to a whole lot more use-cases. It’ll also help you to know what to look out for in the future when debugging, or when using other similar methods such as querySelector() or getElementbyID().


What makes these methods so different?

querySelectorAll() retrieves a list of elements from the document based on your given selector, and returns a static NodeList object.

getElementsByClassName() retrieves a list of elements from the document based on an element’s class name, and returns a live HTML collection of elements.

Because querySelectorAll() returns a list that is static from the moment it is called, its list of items cannot be updated thereafter even if changes are made to the DOM dynamically. Contrast this to getElementsByClassName(), which returns matching sets of elements at any given moment it is called. If you are making changes to the DOM on the fly, the list returned by getElementsByClassName() will be updated dynamically.

MDN states on static NodeLists:

…any changes in the DOM does not affect the content of the collection.

Contrast this to HTML Collections:

An HTMLCollection in the HTML DOM is live; it is automatically updated when the underlying document is changed.

These differences are made more apparent when these methods are used to manipulate dynamically created content, as you’ll see below.


A basic example

Say I wanted to retrieve some instagram images to display on my website.

Typical steps to achieve this will likely involve writing a javascript function requesting the data from the API, then appending that data to the container. The resulting markup in the DOM will look something like this:

 <body>
  ...
  <img src="example.jpg" class="ig-post"> //appended item from Instagram's API
  <img src="example.jpg" class="ig-post"> //appended item from Instagram's API
  <img src="example.jpg" class="ig-post"> //appended item from Instagram's API
  <img src="example.jpg" class="ig-post"> //appended item from Instagram's API
  <img src="example.jpg" class="ig-post"> //appended item from Instagram's API
  ...
 <body>

Now, say I wanted to add a click event to these appended items.

I might try doing something like this:

let igPosts = document.querySelectorAll('ig-post'); //select the appended posts

for(var i = 0; i < igPosts.length ; i++) {
  igPosts[i].addEventListener('click', function() {
    this.setAttribute("style", "border:1px solid currentColor");
  });
}
// this won't work as expected

If you try testing the click function however, you’ll see it won’t work on the appended items. This is because querySelectorAll() will reference the DOM before these items are appended, and the list is never updated thereafter. Therefore, it’s list will never know when the images are appended to the DOM.

If you try the same method again but with getElementsByClassName instead, you’ll find the HTML collection updates as soon as the images are appended and so is able to attach events to the appended items.

let igPosts = document.getElementsByClassName('ig-post'); //select the appended posts

// this will work as expected
for(var i = 0; i < igPosts.length ; i++) {
  igPosts[i].addEventListener('click', function() {
    this.setAttribute("style", "background-color: red;");
  });
}

Remember, using getElementsByClassName() returns a list of items that is dynamically updated as the DOM is updated. This means that when the DOM is updated with appended posts, getElementsByClassName() knows the elements exist on the page and can manipulate them accordingly. This also means that if any element is moved, or updated on the document, getElementsByClassName() will know about it.

For a basic working example, take a look at the CodePen below and open your console to inspect the length of each collection.

Try changing the for loop to use igQuery instead. Notice how it is not able to select the appended item unlike igGetElems.

See the Pen Selecting dynamic elements - querySelector vs getElementsby by Jakki (@electrifried) on CodePen.


Which is better to use?

‘Better’ is subjective, and really depends on how you want to manipulate the elements on your page.

It important to note, you are able to use more complex selectors with querySelectorAll() and combine tags, ids, classes, and pseudo elements together to select a group of elements like the following:

const links = document.querySelectorAll('a[href^="https://"]');

For a more thorough and complex element query, querySelectorAll() often gets the job done, with the caveat being the list is static. (and for more great examples using querySelectorAll(), check out this article.

When using querySelectorAll() you are only able to specify a class:

const items = document.getElementsByClassName('item');

or multiple classes:

const items = document.getElementsByClassName('item gallery-item');

Another important thing to note is, oftentimes event delegation may be the answer to all your problems if you want to use queryselector~ AND watch for any changes on the fly. In fact, this is my preferred method of dealing with dynamic elements and event listeners, but the best option varies with each case.

I will admit, I often prefer to use querySelectorAll() because the returned nodeList object is easier to manupulate. You can also use promises or callback functions alongside querySelectorAll() to achieve the same result as querySelectorAll() but that is a whole other topic in itself.

Lastly, performance-wise I honestly don’t think you’ll notice too much of a difference between using the two, however you can visit this link and make up your own mind on what to use.


Looping over the objects

There are several ways you can iterate over the returned object with both querySelectorAll() and getElementsByClassName(), many of which are documented here.

My go to methods:

For vanilla JS, I just use your typical for loop:

const galleryItems = document.getElementsByClassName('gallery-item');

for (const i = 0; i < galleryItems.length; i++) {
    console.log(galleryItems[i]);
}

If you are using something like Babel or Typescript to process your JavaScript, you can use a foreach loop which is what I personally use all the time as it is the easiest to remember.

const galleryItems = document.querySelectorAll('.gallery-item');

galleryItems.forEach(function(x) {
  return x['src'];
});

And if you’re using jQuery, well you don’t have to worry about this issue too much since you have the awesome and versatile jQuery object