querySelectorAll() vs getElementsByClassName

Published 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.

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 fade out the appended items when the page’s menu is hovered.

I might try doing something like this:

function getIgPosts() {
  //logic to retrieve ig posts goes here
}
getIgPosts(); //call the function and get the images
let igPosts = document.querySelectorAll('.ig-post'); //select the appended posts

//fade the images out when #menu is hovered
document.querySelector('#menu').addEventListener("mouseover", function( event ) {   for (const i = 0; i < igPosts.length; i++) {
    igPosts[i].classList.add('fade-out'); //add the class
  }
});
document.querySelector('#menu').addEventListener("mouseout", function( event ) {   for (const i = 0; i < igPosts.length; i++) {
    igPosts[i].classList.remove('fade-out'); //add the class
  }
});

However if I hover over #menu, the item class lists won’t update. 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 NodeList updates as soon as the images are appended and the images will fade out when #menu is hovered.

function getIgPosts() {
  //logic to retrieve ig posts goes here
}
getIgPosts(); //call the function
let igPosts = document.getElementsByClassName('.ig-post'); //select the appended posts

//fade the images out when #menu is hovered
document.querySelector('#menu').addEventListener("mouseover", function( event ) {   for (const i = 0; i < igPosts.length; i++) {
    igPosts[i].classList.add('fade-out'); //add the class
  }
});
document.querySelector('#menu').addEventListener("mouseout", function( event ) {   for (const i = 0; i < igPosts.length; i++) {
    igPosts[i].classList.remove('fade-out'); //add the class
  }
});

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.

  • Sidenote: There is a way to use querySelectorAll() to return a static list of dynamically created elements, and that is to use it as part of a callback. If you don’t know what that is, don’t worry, it’s not super important in the context of this post. If you’re interested though, you can read this article which I personally think explains them very well.

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() does the job well, with the caveat being the list is static. (and for more great examples using querySelectorAll(), check out this article.

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

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

or multiple classes:

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

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!