14

I wrote a function to change the class of elements to change their properties. For some reason, only some of the elements have changed. It took me a few hours to find a solution, but it seems odd to me. Perhaps you can explain this to me.

This isn’t working:

function replace(){
  var elements = document.getElementsByClassName('classOne');

  for (var i = 0; i < elements.length; i++) {
    elements[i].className = 'classTwo';               
  }
}

See the JSFiddle: only every second item is affected; only every second red element changes color to blue.

So I changed the final expression of the for loop to not increment i anymore:

function replace(){
  var elements = document.getElementsByClassName('classOne');

  for (var i = 0; i < elements.length; i) { // Here’s the difference
    elements[i].className = 'classTwo';               
  }
}

This works well! It seems as though push is called and no increment is needed. Is this normal? It is different from the examples I’ve seen.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
MeNa
  • 1,467
  • 9
  • 23
  • I have never heard the term [“loop coefficient”](https://stackoverflow.com/revisions/15562484/1). JavaScript calls the last part of a `for` loop the [“final expression”](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for#Syntax) (or [“increment”](https://tc39.es/ecma262/#sec-forbodyevaluation) in certain contexts of the spec) and `i++` an [“increment”](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment) (or, generalized, [“UpdateExpression”](https://tc39.es/ecma262/#sec-update-expressions) in the spec). – Sebastian Simon Aug 17 '19 at 08:35
  • Related: [for...in loop not looping through all properties?](/q/32784922/4642212). – Sebastian Simon Nov 17 '22 at 02:40

3 Answers3

21

What's going on is an odd side effect. When you reassign className for each element of elements, the element gets removed from the array! (Actually, as @ user2428118 points out, elements is an array-like object, not an array. See this thread for the difference.) This is because it no longer has the classOne class name. When your loop exits (in the second case), the elements array will be empty.

You could write your loop as:

while (elements.length) {
    elements[0].className = 'classTwo'; // removes elements[0] from elements!
}

In your first case, by incrementing i, you are skipping half of the (original) elements that have class classOne.

Excellent question, by the way. Well-researched and clear.

Community
  • 1
  • 1
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • Thank you! sounds right.. but... it is sense programming? "elements" is an exist object, not dynamically object that can change without you? – MeNa Mar 22 '13 at 04:38
  • 1
    @MeNa - While the `elements` variable is local to your function, the array that it references is also accessible to (and maintained by) the DOM. The `className` property of each element actually triggers a setter function within the DOM machinery. One of the side effects of that function is to remove the element from the array that `elements` references. So it doesn't change "without you"; it changes because you changed the `className` attribute of one of its elements. To quote the sages of old: _"That's not a bug; that's a feature!"_ – Ted Hopp Mar 22 '13 at 04:47
  • @TedHopp - Thank you again! now all is clearly. (PS, thanks about the 'while()', it's a nicely and elegant solution.) – MeNa Mar 22 '13 at 04:55
  • You should probably mention that elements isn't _actually_ an array, but merely an array-like datastructure – user2428118 Dec 13 '16 at 12:12
4

getElementsByClassName returns a NodeList. A NodeList collection is a live collection, which means that the modification of the document affects the collection. more

NoodleFolk
  • 1,949
  • 1
  • 15
  • 24
  • Tnx. Indeed the 'live collection' term explains the point well. – MeNa Mar 22 '13 at 10:49
  • 4
    NOT all NodeList collection is live. Some "fetch" methods such as querySelectorAll(), returns STATIC NodeList which are not affected by subsequent DOM changes. – SHH Dec 27 '14 at 05:25
  • It returns an live [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection) – pilchard Dec 31 '21 at 13:09
-3

Or revert the loop, beginning by length-1 and step down to 0

Gerry
  • 1
  • Yes, that would work as well, but the question begs for an explanation rather than a solution. – Bergi Dec 01 '14 at 15:02