Why is forEach not working for children?
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;
}
erik
Updated on July 09, 2022Comments
-
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 thatdocument.getElementById("niceParent").children
is an array, as I can access the elements withconsole.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 about 11 yearsThat will, however, cause difficulties if combined with the
for..in
approach. -
Felix Kling about 11 yearsI recommend to read the article: "What’s wrong with extending the DOM".
-
erik about 11 yearsI 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 about 11 yearsNevertheless it’s interesting to know that this is possible.
-
Андрей Беньковский almost 8 yearsIn ECMAScript6 one can use
Array.prototype.from()
to state the intent of converting to an Array in a more clear way. -
Herc about 7 yearsWhile 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 almost 6 yearsDon’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 over 5 years
-
aloisdg about 5 yearsYou can do even better with ES6 thanks to the spread operator. Check my answer below :)
-
dehart almost 5 yearsExtending 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 almost 5 yearsThanks for pointing that out @dehart, extended the answer with a better approach.