Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my JavaScript for-loop skipping elements? [duplicate]

I have a for loop that runs through a set of elements, removing the 'selected' class from each. However, it skips over every second iteration. I've found that I can get around this by adding j--, which I guess is fine except for lengthening my code. But I wonder if someone could explain why it skips and perhaps suggest a more succinct way of writing this code? (I'm still learning the ropes and want to make sure I understand what's going on.)

var selections = document.getElementsByClassName(name + 'selected');
for (var j = 0; j < selections.length; j++) {
  selections[j].classList.remove('selected');
  j--; // the fix
}

// where name is a present variable

Thanks for your time!

like image 407
dedaumiersmith Avatar asked Aug 19 '16 15:08

dedaumiersmith


People also ask

Can a for loop have two conditions JavaScript?

It can consist of multiple sub-statements i.e. there can be multiple conditions. For as long as condition evaluates to true , the code written inside the while block keeps on executing. As soon as condition evaluates to false , the loop breaks.

What are the 3 parts of a for loop in JavaScript?

The For Loop Expression 1 is executed (one time) before the execution of the code block. Expression 2 defines the condition for executing the code block. Expression 3 is executed (every time) after the code block has been executed.

Is for loop in JavaScript synchronous?

for loop is synchronous.


2 Answers

This is because getElementsByClassName() returns a live HtmlCollection; in other words, the HtmlCollection automatically updates, so as you remove the class "selected" from an element, that element is removed from the collection.

You can simply do;

var selections = document.getElementsByClassName(name + 'selected');
while (selections.length) {
    selections[0].classList.remove('selected');
}

... instead.


Alternatively, as pointed out by Paul Roub in the comments, you can iterate in reverse;

for (var j = selections.length-1; j >= 0; j--) {
  selections[j].classList.remove('selected');
}

Or, you can avoid a live HtmlCollection completely, either by copying the collection to an array;

var selections = Array.prototype.slice.call(document.getElementsByClassName(name + 'selected'));
for (var j = 0; j < selections.length; j++) {
  selections[j].classList.remove('selected');
}

... or, as pointed out by Yury Tarabanko in the comments, using querySelectorAll instead;

var selections = document.querySelectorAll('.' + name + 'selected');
for (var j = 0; j < selections.length; j++) {
  selections[j].classList.remove('selected');
}
like image 107
Matt Avatar answered Nov 17 '22 04:11

Matt


getElementsByClassName is a live HTML collection so when you remove the class the element, it is updated. So that means what used to be in the index after it is now in the location where you removed the element.

var selections = document.getElementsByClassName('selected');
console.log("before: ", selections.length);
selections[0].classList.remove("selected");
console.log("after: ", selections.length);
<div class="selected"></div>
<div class="selected"></div>
<div class="selected"></div>
<div class="selected"></div>
<div class="selected"></div>

Looping backwards works because the items are not shifting down.

Option option is to do a while loop.

var selections = document.getElementsByClassName('selected');
while(selections.length) {
    selections[0].classList.remove("selected");
}
like image 43
epascarello Avatar answered Nov 17 '22 06:11

epascarello