57

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?

erik
  • 2,278
  • 1
  • 23
  • 30

6 Answers6

93

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);
aw04
  • 10,857
  • 10
  • 56
  • 89
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 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 Feb 26 '13 at 17:53
  • 2
    In ECMAScript6 one can use `Array.prototype.from()` to state the intent of converting to an Array in a more clear way. – Андрей Беньковский Jun 02 '16 at 12:06
  • 1
    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? – Herc Feb 19 '17 at 15:16
  • 4
    Actually [.children](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children) contains an `HTMLCollection`, not a `NodeList`. `NodeList` does have a [forEach](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach) method nowadays, while `HTMLCollection` does not. – BenMorel Sep 18 '18 at 20:34
  • 1
    You can do even better with ES6 thanks to the spread operator. Check my [answer](https://stackoverflow.com/a/52403188/1248177) below :) – aloisdg Feb 18 '19 at 16:40
18

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

aloisdg
  • 22,270
  • 6
  • 85
  • 105
12

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(…);
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
2

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;
}
Zoltan.Tamasi
  • 1,382
  • 13
  • 26
  • That will, however, cause difficulties if combined with the `for..in` approach. – lonesomeday Feb 26 '13 at 17:10
  • 2
    I recommend to read the article: ["What’s wrong with extending the DOM"](http://perfectionkills.com/whats-wrong-with-extending-the-dom/). – Felix Kling Feb 26 '13 at 17:12
  • Nevertheless it’s interesting to know that this is possible. – erik Feb 26 '13 at 17:54
  • 1
    Don’t do this. If you have to extend native prototypes, check for their existence _first_, then [add a _non-enumerable, non-constructible method_](https://stackoverflow.com/a/46491279/4642212) to the prototype. For reference, [`NodeList.prototype.forEach`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach) exists in recent browsers. – Sebastian Simon Jul 25 '18 at 20:09
  • 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" – dehart May 09 '19 at 20:22
  • Thanks for pointing that out @dehart, extended the answer with a better approach. – Zoltan.Tamasi May 13 '19 at 13:58
0

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

Arosha
  • 1,311
  • 2
  • 16
  • 22
0

Perhaps this can be an easy solution:

document.getElementById("niceParent").childNodes.forEach(function(entry) {
  console.log(entry);
});
pusle
  • 1,393
  • 14
  • 18