[javascript] For loop for HTMLCollection elements

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?

This question is related to javascript dom

The answer is


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
}

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);
    }
  }
}

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
});

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()

You can also do like this:

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

_x000D_
_x000D_
let elements = document.getElementsByClassName("classname");
for (let index in elements) {
  if (index <= elements.length) {
    console.log(elements[index]);
  }
}
_x000D_
<div class="classname"> element 1 </div>
<div class="classname"> element 2 </div>
<div class="classname"> element 3 </div>
_x000D_
_x000D_
_x000D_

or

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

_x000D_
_x000D_
let elements = document.getElementsByClassName("classname");
for (let ele of elements) {
  console.log(ele);
}
_x000D_
<div class="classname"> element 1 </div>
<div class="classname"> element 2 </div>
<div class="classname"> element 3 </div>
<div class="classname"> element 4 </div>
_x000D_
_x000D_
_x000D_


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) });

ect...


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


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
    });

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);
    }

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);
});

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);
}


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.