Iterating over result of getElementsByClassName using Array.forEach
Asked Answered
M

14

428

I want to iterate over some DOM elements, I'm doing this:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

but I get an error:

document.getElementsByClassName("myclass").forEach is not a function

I am using Firefox 3 so I know that both getElementsByClassName and Array.forEach are present. This works fine:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

Is the result of getElementsByClassName an Array? If not, what is it?

Mucky answered 6/10, 2010 at 10:28 Comment(0)
B
626

No, it's not an array. As specified in DOM4, it's an HTMLCollection (in modern browsers, at least. Older browsers returned a NodeList).

In all modern browsers (pretty much anything other IE <= 8), you can call Array's forEach method, passing it the list of elements (be it HTMLCollection or NodeList) as the this value:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

If you're in the happy position of being able to use ES6 (i.e. you can safely ignore Internet Explorer or you're using an ES5 transpiler), you can use Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Branen answered 6/10, 2010 at 10:36 Comment(10)
No need to convert it to an Array first. Just use [].forEach.call(elsArray, function () {...}).Joeyjoffre
It's NOT a NodeList. It's an array-like object. I don't even think it has an instance type. querySelectorAll method returns a NodeList though.Veradis
@MaksimVi. You're absolutely right: DOM4 specifies that document.getElementsByClassName() should return an HTMLCollection (which is very similar but not a NodeList). Thanks for pointing out the mistake.Branen
@MaksimVi.: I wonder whether that changed at some point. I usually check these things.Branen
@TimDown, Thanks for HTMLCollection tip. Now I finally can use HTMLCollection.prototype.forEach = Array.prototype.forEach; in my code.Veradis
@MaksimVi.: I did some research. Browser implementations did indeed change from NodeList to HTMLCollection, although I'm not sure exactly when (since 2010, anyway). Therefore HTMLCollection.prototype.forEach = Array.prototype.forEach may not cover every browser you care about.Branen
or just use const els = Array.from(document.getElementsByClassName("myclass"))Calais
@catalint: If you're able to use ES6 then yes, sure. Browser support isn't there yet for most browser-based uses.Branen
I made the following bunch of divs: (first line just for context) $('.behaelter_850ankuendg').css({'width': width}); var els = document.getElementsByClassName("behaelter_850ankuendg"); var i = 0; Array.prototype.forEach.call(els, function(el) { el.css({'left': (i*width)+'px', 'top': (-(i*100))+'%'}); i++ }); doesn't work (el.css not a function). What am I messing up? Taken a bit out of context, but I didn't want to post my whole script lolOrthodoxy
It's not the dynamic css part, it doesn't work with basic value strings either... jQuery works in other places so it's not that.... thank you!!Orthodoxy
B
152

You can use Array.from to convert collection to array, which is way cleaner than Array.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

In older browsers which don't support Array.from, you need to use something like Babel.


ES6 also adds this syntax:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

Rest destructuring with ... works on all array-like objects, not only arrays themselves, then good old array syntax is used to construct an array from the values.


While the alternative function querySelectorAll (which kinda makes getElementsByClassName obsolete) returns a collection which does have forEach natively, other methods like map or filter are missing, so this syntax is still useful:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Biphenyl answered 21/6, 2016 at 10:22 Comment(1)
Note: without transpilling as suggested (Babel), this is NOT compatible in IE < Edge, Opera, Safari < 9, Android browser, Chrome for Android, ...etc) Source: mozilla dev docsChivy
K
67

Or you can use querySelectorAll which returns NodeList:

document.querySelectorAll('.myclass').forEach(...)

Supported by modern browsers (including Edge, but not IE):
Can I use querySelectorAll
NodeList.prototype.forEach()

MDN: Document.querySelectorAll()

Kamerman answered 13/5, 2017 at 8:10 Comment(4)
Bear in mind the performance penalty over getElementByClassNameFreely
The performance penalty is negligible compared to other more intensive tasks like modifying DOM. If I execute 60,000 of these in 1 millisecond, I'm pretty sure it won't be an issue for any reasonable usage :)Kamerman
You linked the wrong benchmark. Here is the correct one measurethat.net/Benchmarks/Show/4076/0/… Just ran it on my low-end phone, got 160k/s vs 380k/s. Since you mentioned DOM manipulation, here is that too measurethat.net/Benchmarks/Show/5705/0/… Got 50k/s vs 130k/s. As you see it's even slower to manipulate DOM, likely due to NodeList being static (as mentioned by others). Still neglegible in most use cases, but almost 3 fold slower nonetheless.Freely
IE doesn't support forEach method for NodeList too. But there is a work around like using spread operator or Array.fromGoldofpleasure
T
19

The result of getElementsByClassName() is not an Array, but an array-like object. Specifically it's called an HTMLCollection, not to be confused with NodeList (which has it's own forEach() method).

One simple way with ES2015 to convert an array-like object for use with Array.prototype.forEach() that hasn't been mentioned yet is to use the spread operator or spread syntax:

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Timekeeper answered 3/10, 2017 at 0:0 Comment(2)
I feel this is really the right way to do it in modern browsers. This is the exact use case spread syntax was created to solve.Triggerhappy
Let me state that I am perhaps the world's most incompetent JavaScript programmer and this was the example that worked for me, out of the box, first try. Thank you very, very much Kloptikus.Ann
H
18

getElementsByClassName returns HTMLCollection in modern browsers.

which is array-like object similar to arguments which is iteratable by for...of loop see below what MDN doc is saying about it:

The for...of statement creates a loop iterating over iterable objects, including: built-in String, Array, Array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables. It invokes a custom iteration hook with statements to be executed for the value of each distinct property of the object.

Javascript Example

for (const element of document.getElementsByClassName("classname")){
   element.style.display="none";
}

Typescript Example

let elements = document.getElementsByClassName('classname');
let i;

for (i = 0; i < elements.length; i++) {

  if (elements[i] instanceof HTMLElement) {
    elements[i].style.display = "none";
  }

}
Huss answered 3/7, 2019 at 6:52 Comment(9)
Not so, according to Typescript: error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.Torquemada
@TurtlesAreCute, Here OP is using javascript not typescript and i have answered in according to vanilla js recommendation so in typescript it can be different solution for the problem.Huss
@TurtlesAreCute, By the way it is also working in typescript also, but you have to mention right type of variable which holds element of particular css class, so it can cast it accordingly, for detail see this answer.Huss
This is easily the best answer and it works fine in Typescript.Spikes
Type 'HTMLCollectionOf<Element>' is not an array type or a string type.Hyperesthesia
@Hyperesthesia yes its not an array or string type, but what i have said is it's an array like objects and its true , see this MDN doc definition of HTMLCollection.Huss
@HaritsinhGohil The code as shown doesn't compile in TypeScript, I posted the compiler error. Though the question is about JS, other commenters have said it works in TS, but it doesn't.Hyperesthesia
@Hyperesthesia what you can do is get this HTMLCollection into the variable and declare it type any and it will work as it works in javascript.Huss
@Hyperesthesia typescript example also added check it in my above answer.Huss
F
16

Edit: Although the return type has changed in new versions of HTML (see Tim Down's updated answer), the code below still works.

As others have said, it's a NodeList. Here's a complete, working example you can try:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

This works in IE 9, FF 5, Safari 5, and Chrome 12 on Win 7.

Fungi answered 29/6, 2011 at 15:51 Comment(1)
Verified Dec 2020 that this code still works.Fungi
P
6

for typescript I prefer iterating for

for(let element of Array.from(document.querySelectorAll('.myclass'))){
   //my code
}
Priscillaprise answered 8/8, 2022 at 19:47 Comment(0)
L
4

As already said, getElementsByClassName returns a HTMLCollection, which is defined as

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

Previously, some browsers returned a NodeList instead.

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

The difference is important, because DOM4 now defines NodeLists as iterable.

According to Web IDL draft,

Objects implementing an interface that is declared to be iterable support being iterated over to obtain a sequence of values.

Note: In the ECMAScript language binding, an interface that is iterable will have “entries”, “forEach”, “keys”, “values” and @@iterator properties on its interface prototype object.

That means that, if you want to use forEach, you can use a DOM method which returns a NodeList, like querySelectorAll.

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

Note this is not widely supported yet. Also see forEach method of Node.childNodes?

Landed answered 23/4, 2016 at 9:30 Comment(4)
Chrome 49 return forEach in not a functionVolumeter
@VitalyZdanevich Try Chromium 50Landed
On Chrome 50 I am getting document.querySelectorAll(...).forEach is not a functionVolumeter
@VitalyZdanevich It worked on Chromium 50, and still works on Chromium 53. Maybe it wasn't considered stable enough to be shipped to Chrome 50.Landed
S
3

Is the result of getElementsByClassName an Array?

No

If not, what is it?

As with all DOM methods that return multiple elements, it is a NodeList, see https://developer.mozilla.org/en/DOM/document.getElementsByClassName

Suiter answered 6/10, 2010 at 10:31 Comment(0)
P
3

This is the safer way:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);
Plante answered 22/6, 2019 at 9:51 Comment(0)
M
3

The simplest solution:

Object.assign(HTMLCollection.prototype, {
  forEach(event) {
    Array.prototype.forEach.call(this, (element) => event(element));
  },
});

After that you can write like this:

document
  .getElementsByClassName("disable")
  .forEach((element) => alert(element.classList[0]));

🙂

Michigan answered 18/7, 2022 at 16:51 Comment(1)
Please do some formatting.Smokeless
M
1

Here is a test I created on jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

The most perfomant version in Chrome and Firefox is the good old for loop in combination with document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

In Safari this variant is the winner:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

If you want the most perfomant variant for all browsers it might be this one:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
Mudcat answered 10/9, 2019 at 13:7 Comment(0)
E
1

Use this code to add forEach method to HTMLCollection

/**
 *
 * @type {Function}
 */
HTMLCollection.prototype.forEach = HTMLCollection.prototype.forEach ||
    function (callBack) {
        for (let i = 0; i < this.length; i++) {
            callBack(this[i])
        }
    };

Then your code will work:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});
Ellga answered 9/5, 2022 at 12:8 Comment(1)
While this works, it is a bad practice to modify prototype.Regenerate
S
0

It does not return an Array, it returns a NodeList.

Sophist answered 6/10, 2010 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.