For loop for HTMLCollection elements

dom, javascript

Solution

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.

Problem

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`?

Original source

Related problems