For loop for HTMLCollection elements

JavascriptDom

Javascript Problem Overview


I'm trying to set get id of all elements in an HTMLCollectionOf. I wrote the following code:

var list = document.getElementsByClassName("events");
console.log(list[0].id);
for (key in list) {
	console.log(key.id);
}

But I got the following output in console:

event1
undefined

which is not what I expected. Why is the second console output undefined but the first console output is event1?

Javascript Solutions


Solution 1 - Javascript

In response to the original question, you are using for/in incorrectly. In your code, key is the index. So, to get the value from the pseudo-array, you'd have to do list[key] and to get the id, you'd do list[key].id. But, you should not be doing this with for/in in the first place.

Summary (added in Dec 2018)

Do not ever use for/in to iterate a nodeList or an HTMLCollection. The reasons to avoid it are described below.

All recent versions of modern browsers (Safari, Firefox, Chrome, Edge) all support for/of iteration on DOM lists such nodeList or HTMLCollection.

Here's an example:

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

To include older browsers (including things like IE), this will work everywhere:

var list = document.getElementsByClassName("events");
for (var i = 0; i < list.length; i++) {
    console.log(list[i].id); //second console output
}

Explanation For Why You Should Not Use for/in

for/in is meant for iterating the properties of an object. That means it will return all iterable properties of an object. While it may appear to work for an array (returning array elements or pseudo-array elements), it can also return other properties of the object that are not what you are expecting from the array-like elements. And, guess what, an HTMLCollection or nodeList object can both have other properties that will be returned with a for/in iteration. I just tried this in Chrome and iterating it the way you were iterating it will retrieve the items in the list (indexes 0, 1, 2, etc...), but also will retrieve the length and item properties. The for/in iteration simply won't work for an HTMLCollection.


See http://jsfiddle.net/jfriend00/FzZ2H/ for why you can't iterate an HTMLCollection with for/in.

In Firefox, your for/in iteration would return these items (all the iterable properties of the object):

0
1
2
item
namedItem
@@iterator
length

Hopefully, now you can see why you want to use for (var i = 0; i < list.length; i++) instead so you just get 0, 1 and 2 in your iteration.


Evolution of Browser Support for NodeList and HTMLCollection iteration

Following below is an evolution of how browsers have evolved through the time period 2015-2018 giving you additional ways to iterate. None of these are now needed in modern browsers since you can use the options described above.

Update for ES6 in 2015

Added to ES6 is Array.from() that will convert an array-like structure to an actual array. That allows one to enumerate a list directly like this:

"use strict";

Array.from(document.getElementsByClassName("events")).forEach(function(item) {
   console.log(item.id);
});

Working demo (in Firefox, Chrome, and Edge as of April 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Update for ES6 in 2016

You can now use the ES6 for/of construct with a NodeList and an HTMLCollection by just adding this to your code:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

Then, you can do:

var list = document.getElementsByClassName("events");
for (var item of list) {
    console.log(item.id);
}

This works in the current version of Chrome, Firefox, and Edge. This works because it attaches the Array iterator to both the NodeList and HTMLCollection prototypes so that when for/of iterates them, it uses the Array iterator to iterate them.

Working demo: http://jsfiddle.net/jfriend00/joy06u4e/.


Second Update for ES6 in Dec 2016

As of Dec 2016, Symbol.iterator support has been built-in to Chrome v54 and Firefox v50 so the code below works by itself. It is not yet built-in for Edge.

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

Working demo (in Chrome and Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Third Update for ES6 in Dec 2017

As of Dec. 2017, this capability works in Edge 41.16299.15.0 for a nodeList as in document.querySelectorAll(), but not an HTMLCollection as in document.getElementsByClassName() so you have to manually assign the iterator to use it in Edge for an HTMLCollection. It is a total mystery why they'd fix one collection type, but not the other. But, you can at least use the result of document.querySelectorAll() with ES6 for/of syntax in current versions of Edge now.

I've also updated the above jsFiddle so it tests both HTMLCollection and nodeList separately and captures the output in the jsFiddle itself.

Fourth Update for ES6 in Mar 2018

Per mesqueeeb, Symbol.iterator support has been built-in to Safari too, so you can use for (let item of list) for either document.getElementsByClassName() or document.querySelectorAll().

Fifth Update for ES6 in Apr 2018

Apparently, support for iterating an HTMLCollection with for/of will be coming to Edge 18 in Fall 2018.

Sixth Update for ES6 in Nov 2018

I can confirm that with Microsoft Edge v18 (that is included in the Fall 2018 Windows Update), you can now iterate both an HTMLCollection and a NodeList with for/of in Edge.

So, now all modern browsers contain native support for for/of iteration of both the HTMLCollection and NodeList objects.

Solution 2 - Javascript

You can't use for/in on NodeLists or HTMLCollections. However, you can use some Array.prototype methods, as long as you .call() them and pass in the NodeList or HTMLCollection as this.

So consider the following as an alternative to jfriend00's for loop:

var list= document.getElementsByClassName("events");
[].forEach.call(list, function(el) {
    console.log(el.id);
});

There's a good article on MDN that covers this technique. Note their warning about browser compatibility though:

> [...] passing a host object (like a NodeList) as > this to a native method (such as forEach) is not guaranteed to work in > all browsers and is known to fail in some.

So while this approach is convenient, a for loop may be the most browser-compatible solution.

Update (Aug 30, 2014): Eventually you'll be able to use ES6 for/of!

var list = document.getElementsByClassName("events");
for (const el of list)
  console.log(el.id);

It's already supported in recent versions of Chrome and Firefox.

Solution 3 - Javascript

In ES6, you could do something like [...collection], or Array.from(collection),

let someCollection = document.querySelectorAll(someSelector)
[...someCollection].forEach(someFn) 
//or
Array.from(collection).forEach(someFn)

Eg:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });

Solution 4 - Javascript

you can add this two lines:

HTMLCollection.prototype.forEach = Array.prototype.forEach;
NodeList.prototype.forEach = Array.prototype.forEach;

HTMLCollection is return by getElementsByClassName and getElementsByTagName

NodeList is return by querySelectorAll

Like this you can do a forEach:

var selections = document.getElementsByClassName('myClass');

/* alternative :
var selections = document.querySelectorAll('.myClass');
*/

selections.forEach(function(element, i){
//do your stuffs
});

Solution 5 - Javascript

There's no reason to use es6 features to escape for looping if you're on IE9 or above.

In ES5, there are two good options. First, you can "borrow" Array's forEach as evan mentions.

But even better...

Use Object.keys(), which does have forEach and filters to "own properties" automatically.

That is, Object.keys is essentially equivalent to doing a for... in with a HasOwnProperty, but is much smoother.

var eventNodes = document.getElementsByClassName("events");
Object.keys(eventNodes).forEach(function (key) {
    console.log(eventNodes[key].id);
});

Solution 6 - Javascript

Alternative to Array.from is to use Array.prototype.forEach.call

forEach: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

map: Array.prototype.map.call(htmlCollection, i => { console.log(i) });

Solution 7 - Javascript

I had a problem using forEach in IE 11 and also Firefox 49

I have found a workaround like this

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }

Solution 8 - Javascript

As of March 2016, in Chrome 49.0, for...of works for HTMLCollection:

this.headers = this.getElementsByTagName("header");

for (var header of this.headers) {
    console.log(header); 
}

See here the documentation.

But it only works if you apply the following workaround before using the for...of:

HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

The same is necessary for using for...of with NodeList:

NamedNodeMap.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

I believe/hope for...of will soon work without the above workaround. The open issue is here:

> https://bugs.chromium.org/p/chromium/issues/detail?id=401699

Update: See Expenzor's comment below: This has been fixed as of April 2016. You don't need to add HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; to iterate over an HTMLCollection with for...of

Solution 9 - Javascript

On Edge

if(!NodeList.prototype.forEach) {
  NodeList.prototype.forEach = function(fn, scope) {
    for(var i = 0, len = this.length; i < len; ++i) {
      fn.call(scope, this[i], i, this);
    }
  }
}

Solution 10 - Javascript

Easy workaround that I always use

let list = document.getElementsByClassName("events");
let listArr = Array.from(list)

After this you can run any desired Array methods on the selection

listArr.map(item => console.log(item.id))
listArr.forEach(item => console.log(item.id))
listArr.reverse()

Solution 11 - Javascript

if you use oldder varsions of ES, (ES5 for example), you can use as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}

Solution 12 - Javascript

You can also do like this:

 let elements = document.getElementsByClassName("classname");
 for(let index in  elements) {
   if(index <= elements.length) {
 		console.log(elements[index]);
  }
}

let elements = document.getElementsByClassName("classname");
for (let index in elements) {
  if (index <= elements.length) {
    console.log(elements[index]);
  }
}

<div class="classname"> element 1 </div>
<div class="classname"> element 2 </div>
<div class="classname"> element 3 </div>

or

 let elements = document.getElementsByClassName("classname");
 for(let ele of  elements) {
 		console.log(ele);
}

let elements = document.getElementsByClassName("classname");
for (let ele of elements) {
  console.log(ele);
}

<div class="classname"> element 1 </div>
<div class="classname"> element 2 </div>
<div class="classname"> element 3 </div>
<div class="classname"> element 4 </div>

Solution 13 - Javascript

You want to change it to

var list= document.getElementsByClassName("events");
console.log(list[0].id); //first console output
for (key in list){
    console.log(list[key].id); //second console output
}

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
Questionuser2953119View Question on Stackoverflow
Solution 1 - Javascriptjfriend00View Answer on Stackoverflow
Solution 2 - JavascriptevanrmurphyView Answer on Stackoverflow
Solution 3 - JavascriptmidoView Answer on Stackoverflow
Solution 4 - JavascriptmmnlView Answer on Stackoverflow
Solution 5 - JavascriptruffinView Answer on Stackoverflow
Solution 6 - JavascriptholmberdView Answer on Stackoverflow
Solution 7 - JavascriptmamosekView Answer on Stackoverflow
Solution 8 - JavascriptMarcGView Answer on Stackoverflow
Solution 9 - JavascriptTiago PertileView Answer on Stackoverflow
Solution 10 - JavascriptCreeptosisView Answer on Stackoverflow
Solution 11 - JavascriptAlon GouldmanView Answer on Stackoverflow
Solution 12 - JavascriptAmaresh S MView Answer on Stackoverflow
Solution 13 - JavascriptAndy897View Answer on Stackoverflow