Loop through childNodes
Asked Answered
R

11

124

I'm trying to loop through childNodes like this:

var children = element.childNodes;
children.forEach(function(item){
    console.log(item);
});

However, it output Uncaught TypeError: undefined is not a function due to forEach function. I also try to use children instead of childNodes but nothing changed.

Does anybody know what's going on?

Rumelia answered 16/7, 2014 at 8:23 Comment(0)
R
169

The variable children is a NodeList instance and NodeLists are not true Array and therefore they do not inherit the forEach method.

Every major browser supports nodeList.forEach


ES5

You can use slice from Array to convert the NodeList into a proper Array.

var array = Array.prototype.slice.call(children);

You could also simply use call to invoke forEach and pass it the NodeList as context.

[].forEach.call(children, function(child) {});


ES6

You can use the from method to convert your NodeList into an Array.

var array = Array.from(children);

Or you can also use the spread syntax ... like so

let array = [ ...children ];


A hack that can be used is NodeList.prototype.forEach = Array.prototype.forEach and you can then use forEach with any NodeList without having to convert them each time.

NodeList.prototype.forEach = Array.prototype.forEach
var children = element.childNodes;
children.forEach(function(item){
    console.log(item);
});

See A comprehensive dive into NodeLists, Arrays, converting NodeLists and understanding the DOM for a good explanation and other ways to do it.

Reconstructionist answered 16/7, 2014 at 8:25 Comment(7)
How could I convert NodeList to pure array?Rumelia
Updated with example but read the link I posted it explains it all :)Reconstructionist
Alternatively, you can do this: [].forEach.call(element.childNodes, child => console.log(child))Caras
Even cooler es6 way: let items = [ ...children ] will turn it into an arrayLatreese
There is a major gotcha with applying Array methods to NodeLists: NodeLists such as node.childNodes are live lists, and if you manipulate the DOM during your loop the NodeList is subject to change, meaning the callback to forEach() my not get called on every element of the list - or more elements than were originally in the list - leading to unpredictable results. It is preferable to turn a NodeList into an array before looping over it.Artamas
Also works: for (let i=0; i < obj.childNodes.length; i++) { const childNode = obj.childNodes[i]; }Jourdan
NodeList.forEach is now supported in every major browser (I've suggested an edit).Hydro
H
42

I'm very late to the party, but since element.lastChild.nextSibling === null, the following seems like the most straightforward option to me:

for(var child=element.firstChild; child!==null; child=child.nextSibling) {
    console.log(child);
}
Haik answered 6/3, 2016 at 21:35 Comment(2)
The most straightforward option is to use regular "for" loop. But your option is interesting.Habitation
I like this the best, was planning to implement the same.. logical and not needing conversionsCocoon
W
31

Here is how you can do it with for-in loop.

var children = element.childNodes;

for(var child in children){
    console.log(children[child]);
}
Wiatt answered 16/7, 2014 at 8:29 Comment(2)
You forgot about check: if (children.hasOwnProperty(child)) { //code here } or you will iterate over unwanted props like "length" and etc.!Habitation
Even better: use for ... of ..., it's ES6 syntax though.Dalmatic
C
7
const results = Array.from(myNodeList.values()).map(parser_item);

NodeList is not Array but NodeList.values() return a Array Iterator, so can convert it to Array.

Calends answered 1/11, 2018 at 13:14 Comment(0)
V
5

Couldn't resist to add another method, using childElementCount. It returns the number of child element nodes from a given parent, so you can loop over it.

for(var i=0, len = parent.childElementCount ; i < len; ++i){
    ... do something with parent.children[i]
    }
Veolaver answered 10/7, 2019 at 9:19 Comment(3)
Beware, parent.childElementCount != parent.childNodes.length. childElementCount returns the number of Element nodes and does not include text and comment nodes, etc. See: w3schools.com/jsref/prop_element_childelementcount.aspEddy
@Eddy As it says in the answer: "returns the number of child element nodes". But it's good emphasizing it.Veolaver
No worries. Technically your answer is correct, it is talking about element count and children, but I just wanted to add the heads-up, since the initial question was about nodes. Too many ways to shoot yourself in the foot :)Eddy
A
4

Try with for loop. It gives error in forEach because it is a collection of nodes nodelist.

Or this should convert node-list to array

function toArray(obj) {
  var array = [];
  for (var i = 0; i < obj.length; i++) { 
    array[i] = obj[i];
  }
return array;
}

Or you can use this

var array = Array.prototype.slice.call(obj);
Assertive answered 16/7, 2014 at 8:25 Comment(0)
L
4

Here is a functional ES6 way of iterating over a NodeList. This method uses the Array's forEach like so:

Array.prototype.forEach.call(element.childNodes, f)

Where f is the iterator function that receives a child nodes as it's first parameter and the index as the second.

If you need to iterate over NodeLists more than once you could create a small functional utility method out of this:

const forEach = f => x => Array.prototype.forEach.call(x, f);

// For example, to log all child nodes
forEach((item) => { console.log(item); })(element.childNodes)

// The functional forEach is handy as you can easily created curried functions
const logChildren = forEach((childNode) => { console.log(childNode); })
logChildren(elementA.childNodes)
logChildren(elementB.childNodes)

(You can do the same trick for map() and other Array functions.)

Longevity answered 28/2, 2018 at 1:39 Comment(0)
C
3

Try this [reverse order traversal]:

var childs = document.getElementById('parent').childNodes;
var len = childs.length;
if(len --) do {
    console.log('node: ', childs[len]);
} while(len --);

OR [in order traversal]

var childs = document.getElementById('parent').childNodes;
var len = childs.length, i = -1;
if(++i < len) do {
    console.log('node: ', childs[i]);
} while(++i < len);
Commendatory answered 16/7, 2014 at 8:32 Comment(1)
Simple for loop is more readable than while loop. Author doesn't ask for the reverse/inverse order traversal.Habitation
F
0

If you do a lot of this sort of thing then it might be worth defining the function for yourself.

if (typeof NodeList.prototype.forEach == "undefined"){
    NodeList.prototype.forEach = function (cb){
        for (var i=0; i < this.length; i++) {
            var node = this[i];
            cb( node, i );
        }
    };
}
Fayre answered 15/2, 2017 at 5:0 Comment(0)
C
0
for (var i = 0; i < e.childNodes.length; i++) {
        var id = e.childNodes[i].id;
    }
Cluck answered 1/12, 2022 at 4:38 Comment(3)
There's no need to use var anymore, and for...of is much cleaner, I think.Phobos
for...of is much cleaner ??? And red is the most beautiful color... The question is: Loop through childNodes. And my for loop does it very effectively, very C-style..Cluck
I'm not saying it doesn't work. I'm just saying that this style of looping over iterable objects and the usage of var for variable declaration in JS is a little dated. Does writing it C-style offer a big advantage?Phobos
T
-1

Just Change children to [...children] to convert NodeList to the array of Nodes, so .forEach can be worked.

let element = document.querySelector('ul');
var children = element.childNodes;
[...children].forEach(function(item){
    console.log(item);
});
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>
Tauromachy answered 5/4, 2023 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.