The title of this post is what I originally googled. Here's what got me there:
I was working on displaying local times for the event listings on dev.to/events (haven't made a PR yet). To do this, I added a class to all elements with a timestamp, like this:
<span class="utc-time"><%= event.starts_at %></span>
I wanted to grab all the timestamps on the page, loop through them, and update their innerHTML to reflect the local time. I usually use for statements when I need to loop stuff, but I decided to try the .forEach function.
var timestamps = document.getElementsByClassName("utc-time");
timestamps.forEach(function(timestamp) {
localTime = updateLocalTime(timestamps[i].innerHTML);
timestamps[i].innerHTML = localTime;
});
I got this error:
Eventually, I realized that timestamps was not an array, it was a NodeList and at the top of mdn documentation, it clearly states:
Although NodeList is not an Array, it is possible to iterate on it using
forEach(). It can also be converted to an Array usingArray.from().However some older browsers have not yet implemented
NodeList.forEach()norArray.from(). But those limitations can be circumvented by usingArray.prototype.forEach()(more in this document).
I probably should have googled "How to loop through a NodeList" for specificity. Anyway, so then I wrote this:
Array.prototype.forEach.call(timestamps, function (timestamp) {
localTime = updateLocalTime(timestamp.innerHTML);
timestamp.innerHTML = localTime;
});
And it worked! But when I showed it to @maestromac, he told me that a simple for statement would have worked. And would probably be a bit safer. So I went back to what I was most familiar with:
for (var i = 0; i < timestamps.length; i++) {
localTime = updateLocalTime(timestamps[i].innerHTML);
timestamps[i].innerHTML = localTime
}
At least I learned something about NodeLists today ¯_(ツ)_/¯

Oldest comments (24)
Ah this is a classic rites of passage in DOM unintutiveness.
If only
NodeListinherited fromArray...There are some methods that don't make much sense in relation to
NodeLists. Mostly mutating methods likepush,sortorsplice.The others are fine, though. I'd love to have
maporfilter😞I think you can also do:
Haven't tested it though 😊
Well, in the mdn documentation it had mentioned this:
So I assumed that if forEach wasn't supported, Array.from wouldn't be either?
I just tested it and it works on Chrome for me.
It might not be compatible with many browsers though, as you mentioned.
You don't have to go far - if you have Windows and IE11 installed, that doesn't support either 😉
The MDN documentation also has an Array.from polyfill:
developer.mozilla.org/en-US/docs/W...
Wait, is this true?
I seldom use
selectElementsByClassName, but according to mdn it returns aHTMLCollection. And the mdn doc clearly says it hasn't gotforEach.But for
NodeList, you should be able to doforEach. If you dodocument.querySelectorAll('.className'), you will get anNodeListand you should be able to doforEach. See here.Howeverrrrr, since most older browsers won't have
NodeList.prototype.forEachdefined, it is probably safer to do what you suggestedArray.prototype.forEach.call(elements, ...)or just[].forEach.call(elements, ...). A more "es6" way would probably beArray.from(elements).forEach(...).Orrrrr, you could do it with for-loops as you suggested. "es6" introduced this amazing
for-ofloop, it could loop through most list-like things. So the following would work as well.Of course, to safely use ES6 features you probably want your polyfills + babel set up to support old browsers.
use the "…" spread operator, which works for both
getElementsByClassName()andquerySelectorAll()so you can port easily later.developer.mozilla.org/en-US/docs/W...
Isn't
NodeList.prototype.forEachexists?Guess her browser hasn't implemented it
A small correction: you used
document.getElementsByClassNamewhich does not return aNodeListbut aHTMLCollection. Now, the former does haveforEachdefined - but it's pretty much the only array method that has been added to its prototype so far.But it's only a relatively recent addition, so older browsers don't support it - fortunately, the
Array#forEachtrick works pretty well, down to sufficiently old Internet Explorer versions (probably6? 5.5?The heck am I saying, that could work forslice, butforEachwas added only in IE9...).A
HTMLCollectionis a totally different beast... and something that should be avoided in general. It's a live collection that gets updated when the DOM changes. Quite heavy when it comes to memory consumption and CPU usage.Conclusion: use
document.querySelectorAllinstead (which returns aNodeList).I don't agree with that ¯\_(ツ)_/¯
It's true that every browser supports
for(duh!), but experience proved that something that iterates over a collection for us is simpler as it doesn't force us to take care of a variable for counting, while the (relatively) complex - although well-known - syntax offoris prone to mistakes.Mostly caused by distraction and/or boredom 😁
A best practice is to convert a
NodeListto a normalArray, so you can use the built-in forEach/map functions.One ES5 way is to create a helper function, for example on the NodeList prototype:
Another popular way is to create a jQuery-ish helper function that wraps the conversion to array. This is also used in Lea Verou's bliss.js library:
The modern ES7+ way is to use the
...operator:Although it's a very good point that you can use a for-loop in this case, your own solution can be simplified to [].forEach.call, which would also work in case you'd need other array operations such as map, filter or reduce.
I always seem to forget that .forEach() only works on Arrays and not objects. Thank you