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
NodeList
inherited fromArray
...There are some methods that don't make much sense in relation to
NodeList
s. Mostly mutating methods likepush
,sort
orsplice
.The others are fine, though. I'd love to have
map
orfilter
π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 anNodeList
and you should be able to doforEach
. See here.Howeverrrrr, since most older browsers won't have
NodeList.prototype.forEach
defined, 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-of
loop, 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.forEach
exists?Guess her browser hasn't implemented it
A small correction: you used
document.getElementsByClassName
which does not return aNodeList
but aHTMLCollection
. Now, the former does haveforEach
defined - 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#forEach
trick works pretty well, down to sufficiently old Internet Explorer versions (probably6? 5.5?The heck am I saying, that could work forslice
, butforEach
was added only in IE9...).A
HTMLCollection
is 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.querySelectorAll
instead (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 offor
is prone to mistakes.Mostly caused by distraction and/or boredom π
A best practice is to convert a
NodeList
to 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