Why is forEach not working for children?

37,498

Solution 1

Because .children contains an HTMLCollection [MDN], not an array. An HTMLCollection object is an array-like object, which exposes a .length property and has numeric properties, just like arrays, but it does not inherit from Array.prototype and thus is not an array.

You can convert it to an array using Array.prototype.slice:

var children = [].slice.call(document.getElementById(...).children);

ECMAScript 6 introduces a new API for converting iterators and array-like objects to real arrays: Array.from [MDN]. Use that if possible since it makes the intent much clearer.

var children = Array.from(document.getElementById(...).children);

Solution 2

A way to convert a HTMLCollection like .children to an array to use forEach() (or map(), etc.) is to use the spread syntax ... in an array [].

var children = [...document.getElementById('x').children];

for example:

[...document.getElementById('x').children].forEach(child => console.log(child))

This is an es6 feature. It will work on all modern browser.

[...document.getElementById('niceParent').children].forEach(child => console.log(child.textContent))
<div id="niceParent">
  <div>a</div>
  <div>b</div>
  <div>c</div>
  <div>d</div>
</div>

If, on visual studio code, you faced the error:

Type 'IterableIterator' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.

Instead of

[...document.getElementById('niceParent').children]

you can rely on

Array.from(document.getElementById('niceParent').children)

More on downlevelIteration

Solution 3

Element.children is not an array. It is an object called an HTMLCollection. These do not have an array’s methods (though they do have the length property).

To loop through it, you'll have to convert it into an array, which you can do using Array.prototype.slice:

var children = Array.prototype.slice.call(document.getElementById("niceParent").children);

children.forEach(…);

Solution 4

You can also do this:

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;

And after this you can call forEach on your collection:

document.getElementById("niceParent").children.forEach(...)

The best and most secure way would be actually to only add forEach in cases when it doesn't already exist:

if (window.NodeList && !NodeList.prototype.forEach) {
   NodeList.prototype.forEach = Array.prototype.forEach;
}
if (window.HTMLCollection && !HTMLCollection.prototype.forEach) {
   HTMLCollection.prototype.forEach = Array.prototype.forEach;
}
Share:
37,498
erik
Author by

erik

Updated on July 09, 2022

Comments

  • erik
    erik almost 2 years

    I have a <div> with some child <div> in it. E.g.

    <div id="niceParent">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
    

    I tried to loop through them with the forEach function, because I thought that document.getElementById("niceParent").children is an array, as I can access the elements with

    console.log(document.getElementById("niceParent").children[1]);
    console.log(document.getElementById("niceParent").children[2]);
    console.log(document.getElementById("niceParent").children[3]);
    console.log(document.getElementById("niceParent").children[4]);
    

    Hence I tried

    document.getElementById("niceParent").children.forEach(function(entry) {
      console.log(entry);
    });
    

    which is not working. I get

    TypeError: document.getElementById(...).children.forEach is not a function
    

    As a workaround I also tried it with a—much more complicated—for..in loop:

    for (var i in document.getElementById("niceParent").children) {
      if (document.getElementById("niceParent").children[i].nodeType == 1) console.log(document.getElementById("niceParent").children[i]);
    }
    

    which worked as expected.

    Why?

  • lonesomeday
    lonesomeday about 11 years
    That will, however, cause difficulties if combined with the for..in approach.
  • Felix Kling
    Felix Kling about 11 years
    I recommend to read the article: "What’s wrong with extending the DOM".
  • erik
    erik about 11 years
    I would like to accept both Felix Kling and lonesomeday answer, as they are the same despite some aliases used. Thank you. Especially for the link to MDN.
  • erik
    erik about 11 years
    Nevertheless it’s interesting to know that this is possible.
  • Андрей Беньковский
    Андрей Беньковский almost 8 years
    In ECMAScript6 one can use Array.prototype.from() to state the intent of converting to an Array in a more clear way.
  • Herc
    Herc about 7 years
    While Array.from or slice are technically possible, surely the conversion process involved makes these considerably less efficient than simply traversing the original NodeList with a for loop?
  • Sebastian Simon
    Sebastian Simon almost 6 years
    Don’t do this. If you have to extend native prototypes, check for their existence first, then add a non-enumerable, non-constructible method to the prototype. For reference, NodeList.prototype.forEach exists in recent browsers.
  • BenMorel
    BenMorel over 5 years
    Actually .children contains an HTMLCollection, not a NodeList. NodeList does have a forEach method nowadays, while HTMLCollection does not.
  • aloisdg
    aloisdg about 5 years
    You can do even better with ES6 thanks to the spread operator. Check my answer below :)
  • dehart
    dehart almost 5 years
    Extending the DOM with a good polyfill is not bad practice in my opinion. I would recommend this answer. @SebastianSimon The MDN page you've linked lists this answer as a Polyfill: "The above behavior is how many browsers actually implement NodeList.prototype.forEach"
  • Zoltan.Tamasi
    Zoltan.Tamasi almost 5 years
    Thanks for pointing that out @dehart, extended the answer with a better approach.