Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

removing childNodes using node.childNodes.forEach

Traditionally, a suggested way of removing a node's children in Javascript is to do something like this:

while(node.firstChild) {
    node.removeChild(node.firstChild);
}

Recently, I attempted to remove all of a node's children using the built in forEach() method:

node.childNodes.forEach(child => {
    node.removeChild(child);
}

This didn't function as I expected. Instead of removing all child nodes, the forEach stopped executing, leaving nodes leftover. What I ended up having to do was use Array.from:

Array.from(node.childNodes)

And then I could remove nodes with forEach. The reason I could not use the traditional method mentioned above is because for some reason, one child was always left behind, causing an infinite loop.

Why does the childNodes.forEach method not remove all the nodes as I thought it would? What am I misunderstanding?

like image 752
m.chiang Avatar asked Dec 06 '22 12:12

m.chiang


2 Answers

node.childNodes is a live collection. As you remove items from it, the collection itself is modified (live while you're iterating). Trying to iterate it as you are, causes elements to be removed from the collection and moved down in the array-like structure while you're iterating it, causing you to miss nodes.

As an example, when you call removeChild() on the 2nd element in the collection, that element itself is then removed from the collection. That causes what was the 3rd element to be moved into the spot in the collection where the 2nd element was. Now, your loop moves on to the 3rd element in the collection. But, that will skip over the element that is now in the 2nd position causing you to never remove it.

That means the only safe way to iterate through the actual collection and remove things is with a backwards traversal because removing things form the end does not cause other elements to change their position in the collection. Removing items from the front (which is what you were doing) does cause items to move in the collection.

Array.from() converts the live collection to a static array where items are not removed from the array while deleting items from the DOM.

I have a personal rule of DOM development to NEVER use a live collection while I'm modifying the DOM in any way because the danger that the live collection gets modified while I'm trying to use it is too high. Array.from() is a very simple way to get a copy of a live collection that's static and is safe to work with, even as the DOM is being modified.


Another safe way to delete them all is with this backwards iteration because items are removed from the end of the live collection which doesn't cause any items to move in the collection that you haven't yet processed:

for (let i = node.childNodes.length - 1; i >= 0; i--) {
   node.removeChild(node.childNodes[i]);
}

But, I generally find this more cumbersome than just converting to a static array with Array.from() as you've already discovered.

like image 118
jfriend00 Avatar answered Dec 08 '22 02:12

jfriend00


node.childNodes is a live collection, so when you remove a child from node in the forEach you mess with the iterator.

https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes

like image 37
kLabz Avatar answered Dec 08 '22 02:12

kLabz