JS: Überlaufen des Ergebnisses von getElementsByClassName mit Array.forEach

240

Ich möchte einige DOM-Elemente durchlaufen. Ich mache Folgendes:

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

aber ich bekomme eine Fehlermeldung:

document.getElementsByClassName ("myclass"). forEach ist keine Funktion

Ich bin mit Firefox 3 , so ich , dass beide wissen , getElementsByClassNameund Array.forEachvorhanden sind. Das funktioniert gut:

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

Ist das Ergebnis getElementsByClassNameeines Arrays? Wenn nicht, was ist das?

Steve Claridge
quelle

Antworten:

383

Nein. Wie in DOM4 angegeben , handelt es sich um eine HTMLCollection(zumindest in modernen Browsern. Ältere Browser haben a zurückgegeben NodeList).

In allen modernen Browsern (so ziemlich alles andere IE <= 8) können Sie die Array- forEachMethode aufrufen und ihr die Liste der Elemente (sei es HTMLCollectionoder NodeList) als thisWert übergeben:

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

Wenn Sie in der glücklichen Lage sind, ES6 verwenden zu können (dh Sie können Internet Explorer ignorieren oder einen ES5-Transpiler verwenden), können Sie Folgendes verwenden Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Tim Down
quelle
29
Sie müssen es nicht zuerst in ein Array konvertieren. Einfach benutzen [].forEach.call(elsArray, function () {...}).
Kay - SE ist böse
1
Es ist KEINE NodeList. Es ist ein Array-ähnliches Objekt. Ich glaube nicht einmal, dass es einen Instanztyp hat. querySelectorAllMethode gibt jedoch eine NodeList zurück.
Maksim Vi.
2
@MaksimVi. Sie haben absolut Recht: DOM4 gibt an, dass ein zurückgegeben werden document.getElementsByClassName()soll HTMLCollection(was sehr ähnlich ist, aber keine NodeList). Vielen Dank, dass Sie auf den Fehler hingewiesen haben.
Tim Down
@MaksimVi.: Ich frage mich, ob sich das irgendwann geändert hat. Normalerweise überprüfe ich diese Dinge.
Tim Down
1
@ TimDown, Danke für den HTMLCollectionTipp. Jetzt kann ich endlich HTMLCollection.prototype.forEach = Array.prototype.forEach;meinen Code verwenden.
Maksim Vi.
70

Sie können verwenden Array.from, um Sammlung in Array zu konvertieren, was viel sauberer ist als Array.prototype.forEach.call:

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

In älteren Browsern, die dies nicht unterstützen Array.from, müssen Sie etwas wie Babel verwenden.


ES6 fügt auch diese Syntax hinzu:

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

Die Rest-Destrukturierung ...funktioniert mit allen Array-ähnlichen Objekten, nicht nur mit den Arrays selbst. Dann wird eine gute alte Array-Syntax verwendet, um ein Array aus den Werten zu erstellen.


Während die alternative Funktion querySelectorAll(die irgendwie getElementsByClassNameveraltet ist) eine Sammlung zurückgibt, die forEachnativ vorhanden ist, fehlen andere Methoden mapoder filterfehlen, sodass diese Syntax immer noch nützlich ist:

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

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Athari
quelle
6
Hinweis: Ohne Transpilling wie vorgeschlagen (Babel) ist dies NICHT kompatibel mit IE <Edge, Opera, Safari <9, Android-Browser, Chrome für Android usw.). Quelle: Mozilla Dev Docs
Sean
30

Oder Sie können verwenden, querySelectorAllwelche NodeList zurückgibt :

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

Unterstützt von modernen Browsern (einschließlich Edge, aber nicht IE):
Kann ich querySelectorAll NodeList.prototype.forEach () verwenden?

MDN: Document.querySelectorAll ()

icl7126
quelle
4
Denken Sie an die Leistungsstrafe über getElementByClassName
Szabolcs Páll
3
Der Leistungsverlust ist im Vergleich zu anderen intensiveren Aufgaben wie dem Ändern von DOM vernachlässigbar . Wenn ich 60.000 davon in 1 Millisekunde ausführe , bin ich mir ziemlich sicher, dass es kein Problem für eine vernünftige Verwendung ist :)
icl7126
1
Sie haben den falschen Benchmark verknüpft. Hier ist die richtige messethat.net/Benchmarks/Show/4076/0/… Ich habe sie gerade auf meinem Low-End-Telefon ausgeführt und habe 160k / s gegenüber 380k / s. Da Sie die DOM-Manipulation erwähnt haben, ist hier auch das, was gemessen wurde.net/Benchmarks/Show/5705/0/… Got 50k / s vs 130k / s. Wie Sie sehen, ist die Manipulation von DOM noch langsamer, wahrscheinlich weil NodeList statisch ist (wie von anderen erwähnt). In den meisten Anwendungsfällen immer noch vernachlässigbar, aber dennoch fast dreimal langsamer.
Szabolcs Páll
14

Bearbeiten: Obwohl sich der Rückgabetyp in neuen HTML-Versionen geändert hat (siehe Tim Downs aktualisierte Antwort), funktioniert der folgende Code weiterhin.

Wie andere gesagt haben, ist es eine NodeList. Hier ist ein vollständiges, funktionierendes Beispiel, das Sie ausprobieren können:

<!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>

Dies funktioniert in IE 9, FF 5, Safari 5 und Chrome 12 unter Win 7.

james.garriss
quelle
9

Das Ergebnis von getElementsByClassName()ist kein Array, sondern ein Array-ähnliches Objekt . Insbesondere heißt es ein HTMLCollection, nicht zu verwechseln mit NodeList( das hat seine eigene forEach()Methode ).

Eine einfache Möglichkeit mit ES2015, ein Array-ähnliches Objekt für die Verwendung zu konvertieren Array.prototype.forEach(), das noch nicht erwähnt wurde, ist die Verwendung des Spread-Operators oder der Spread-Syntax :

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

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
quelle
2
Ich denke, dies ist wirklich der richtige Weg, dies in modernen Browsern zu tun. Dies ist der genaue Anwendungsfall, für den die Spread-Syntax erstellt wurde.
Matt Korostoff
3

Wie bereits erwähnt, wird getElementsByClassNameeine HTMLCollection zurückgegeben , die als definiert ist

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

Zuvor haben einige Browser stattdessen eine NodeList zurückgegeben .

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

Der Unterschied ist wichtig, weil DOM4 jetzt definiert NodeList s als iterable.

Laut Web IDL- Entwurf

Objekte, die eine Schnittstelle implementieren, die als iterierbar deklariert ist, unterstützen die Iteration, um eine Folge von Werten zu erhalten.

Hinweis : In der ECMAScript-Sprachbindung enthält eine iterierbare Schnittstelle die Eigenschaften "Einträge", "für jeden", "Schlüssel", "Werte" und @@ Iterator für das Prototypobjekt der Schnittstelle .

Das bedeutet, dass Sie, wenn Sie verwenden möchten, forEacheine DOM-Methode verwenden können, die eine NodeList zurückgibt , wie z querySelectorAll.

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

Beachten Sie, dass dies noch nicht allgemein unterstützt wird. Siehe auch für jede Methode von Node.childNodes?

Oriol
quelle
1
Chrome 49 RückkehrforEach in not a function
Vitaly Zdanevich
@VitalyZdanevich Versuchen Sie Chrom 50
Oriol
Auf Chrome 50 document.querySelectorAll(...).forEach is not a function
bekomme
@VitalyZdanevich Es funktionierte auf Chrom 50 und funktioniert immer noch auf Chrom 53. Vielleicht wurde es nicht als stabil genug angesehen, um an Chrome 50 geliefert zu werden.
Oriol
1

Es gibt kein zurück Array, es gibt ein zurück NodeList .

reko_t
quelle
1

Dies ist der sicherere Weg:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);
gildniy
quelle
0

getElementsByClassNamekehrt HTMLCollection in modernen Browsern.

Dies ist ein Array-ähnliches Objekt, das Argumenten ähnelt, die durch eine for...ofSchleife iteriert werden können. Siehe unten, was das MDN- Dokument dazu sagt:

Die for ... of-Anweisung erstellt eine Schleife, die über iterierbare Objekte iteriert , einschließlich: integrierter String, Array, Array-ähnlicher Objekte (z. B. Argumente oder NodeList), TypedArray, Map, Set und benutzerdefinierte Iterables. Es ruft einen benutzerdefinierten Iterations-Hook mit Anweisungen auf, die für den Wert jeder einzelnen Eigenschaft des Objekts ausgeführt werden sollen.

Beispiel

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Haritsinh Gohil
quelle
Nicht so laut Typoskript:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Schildkröten sind
@TurtlesAreCute, hier verwendet OP Javascript, nicht Typoskript, und ich habe gemäß der Empfehlung von Vanilla Js geantwortet, sodass es in Typoskript eine andere Lösung für das Problem sein kann.
Haritsinh Gohil
@TurtlesAreCute, Übrigens funktioniert es auch in Typoskript, aber Sie müssen den richtigen Variablentyp erwähnen, der das Element einer bestimmten CSS-Klasse enthält, damit es entsprechend umgewandelt werden kann. Einzelheiten finden Sie in dieser Antwort .
Haritsinh Gohil
0

Hier ist ein Test, den ich auf jsperf erstellt habe: https://jsperf.com/vanillajs-loop-through-elements-of-class

Die leistungsstärkste Version in Chrome und Firefox ist die gute alte for-Schleife in Kombination mit document.getElementsByClassName:

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

In Safari ist diese Variante der Gewinner:

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

Wenn Sie die leistungsstärkste Variante für alle Browser wünschen, ist dies möglicherweise die folgende:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
StefanSL
quelle