Why is forEach not working for children?

JavascriptFor LoopForeachParent Child

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 3 - Javascript

A cleaner and more modern 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>

Solution 4 - Javascript

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;
}

Solution 5 - Javascript

If you need a clean approach with a lightweight npm module to resolve above issue, please check this out https://www.npmjs.com/package/foreach-array

Ex:

import each from 'foreach-array';

const array = ['First Name', 'Last Name', 'Country'];

each(array, (value, index, array) => {
    console.log(index + ': ' + value);
});

// Console log output will be:
//      0: First Name
//      1: Last Name
//      2: Country

For your scenario it is document.getElementById("niceParent").children instead of array in the above example

Solution 6 - Javascript

Perhaps this can be an easy solution:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionerikView Question on Stackoverflow
Solution 1 - JavascriptFelix KlingView Answer on Stackoverflow
Solution 2 - JavascriptlonesomedayView Answer on Stackoverflow
Solution 3 - JavascriptaloisdgView Answer on Stackoverflow
Solution 4 - JavascriptZoltan.TamasiView Answer on Stackoverflow
Solution 5 - JavascriptAroshaView Answer on Stackoverflow
Solution 6 - JavascriptpusleView Answer on Stackoverflow