4

When I load my page, a nodeList gets created, and it looks like this:

[text, h4, text, span, br, input, br, span, br, input, br, span, br, input, br, span, br, input, br]

I created a simple for loop that loops through all these elements and deletes each one of them from the DOM. (all the elements are in a <section>)

Here's the loop:

        for(element in videoTitlesElement.childNodes){
            if(!isNaN(element)){
                videoTitlesElement.removeChild(
                        videoTitlesElement.childNodes[element]);
            }
        }

But, by the end of the loop, the nodeList looks like this:

[h4, span, input, span, input, span, input, span, input]

not all elements got removed. Why?

Thanks.

Graham Russell
  • 997
  • 13
  • 24
bool3max
  • 2,748
  • 5
  • 28
  • 57
  • 2
    Because you are modifying the collection when looping through it. Let's say you have three elements in a collection `[A, B, C]`. A `for..in` loop will internally have an indexer to keep track of the current element. So the indexer is 0 at start which points to element `A`. Then you remove the first element and the second element, `B`, now becomes the first. The indexer is now advanced and points to 1. Now the element at index 1, `C`, is removed. And as you might see the element at index 0, `B`, is untouched and skipped over. – Sani Huttunen Sep 25 '15 at 14:58
  • Related: [Strange behavior when iterating over HTMLCollection from getElementsByClassName](/q/15562484/4642212). – Sebastian Simon Nov 17 '22 at 02:40

3 Answers3

12

Two things. First, don't use for ... in when you're iterating through numeric indexes; use a plain for loop. Then you won't need that isNaN() check, and it's generally safer.

The second problem is that when you remove a child, you change the length of the list. If you remove child 0, then the child that used to be child 1 becomes child 0. Thus, what you really want is a simple while loop:

while (videoTitlesElement.childNodes.length)
  videoTitlesElement.removeChild(videoTitlesElement.childNodes[0]);

or, simpler:

while (videoTitlesElement.firstChild)
  videoTitlesElement.removeChild(videoTitlesElement.firstChild);

I should also note (assuming you're working with an HTML DOM) that it's easier to clear out all the child nodes of an element by simply blasting it via .innerHTML:

videoTitlesElement.innerHTML = "";
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • The simpler case is almost correct, but here: `.removeChild(firstChild)` the `firstChild` is undefined. – pawel Sep 25 '15 at 15:00
  • Thank you! Great explanation. "Clearing" the .innerHTML isn't the best in my case, and I also read that removing elements one by one is generally a lot faster. – bool3max Sep 25 '15 at 15:05
  • @aCodingN00b I would be very surprised to learn that removing nodes one by one is faster than just clearing `.innerHTML`. – Pointy Sep 25 '15 at 15:06
  • @Pointy http://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript – bool3max Sep 25 '15 at 15:08
  • @aCodingN00b That question is old and I don't believe most of those comments. – Pointy Sep 25 '15 at 15:10
  • @StriplingWarrior I'm not sure I trust the original revision of that test; it's a hard thing to measure because it's hard to avoid counting the cost of rebuilding the DOM after clearing it on each test iteration. – Pointy Sep 25 '15 at 17:49
  • @Pointy: Good point. I suppose the performance difference is unlikely to be noticeable either way. – StriplingWarrior Sep 25 '15 at 19:48
4

The list being iterated over (videoTitlesElement.childNodes) is being mutated during iteration. Try iterating over a copy of the original list:

var children = [].slice.call(videoTitlesElement.childNodes);

for(element in children){
    if(!isNaN(element)){
         videoTitlesElement.removeChild(videoTitlesElement.childNodes[element]);
    }
}
rjz
  • 16,182
  • 3
  • 36
  • 35
3

If you look at your results, you'll note that you're skipping every second item.

If you imagine this as a for loop with an iterator, you're incrementing your iterator each time, but removing an item from the same collection before incrementing again. That means the item that was at index 1 when you first start the loop is at index 0 after you remove the first item in the array. So when you go to look at index 1 you've skipped over that element. This continues until the end of the loop, so you miss every second item.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315