A trap when looping on getElementsByClassName()

Here is a tip to save the JavaScript coders out there some headaches.  Suppose you want to loop over all elements with a class name like “highlighted” and change the class to something like “normal”.  You might be tempted to do this:

 // This won't work the way you expect it to!
var elements = document.getElementsByClassName('highlighted');
for (var i = 0; i < elements.length; ++i)
   elements[i].setAttribute('class', 'normal');

The problem is that “elements” is not your normal everyday array.  When you change the class for one of the elements from “highlighted” to “normal” the elements object is updated to remove that element.  So, the next element you were going to change slips down into the position that was previously occupied by the one you just changed and you end up missing it with your loop.  Only every other element gets modified.  The fix is to run the loop backwards:

for (var i = elements.length - 1; i >= 0; --i)
   elements[i].setAttribute('class', 'normal');

Also, keep in mind that Internet Explorer (as of version 9) doesn’t have getElementsByClassName(), so you’ll have to write your own.  You can test to see if the browser lacks the function by comparing document.getElementsByClassName to null (current versions of Firefox, Chrome, Opera, and Safari all have it).

5 thoughts on “A trap when looping on getElementsByClassName()

  1. Justin

    Hi quick question,

    What if I am fetching using querySelectorAll instead? That also returns an array, but does it need to be looped through backwards like getElementsByClassName does?

    1. Bill Dimm

      Hi Justin,

      I just tried it on several browsers (Firefox, Konqueror, Chrome, Opera, Safari, and IE) and querySelectorAll() does NOT need the backwards loop for any of them. I don’t know if there is a spec that guarantees that it avoids the issue that getElementsByClassName() has, but it seems to be safe with current browser implementations.


Leave a Reply