For-Schleife für HTMLCollection-Elemente

405

Ich versuche, die ID aller Elemente in einem abzurufen HTMLCollectionOf. Ich habe folgenden Code geschrieben:

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

Aber ich habe die folgende Ausgabe in der Konsole erhalten:

event1
undefined

Das habe ich nicht erwartet. Warum wird die zweite Konsole ausgegeben undefined, die erste jedoch event1?

Neuron
quelle
23
Warum steht im Titel "foreach", wenn es um die for ... in-Anweisung geht? Ich bin zufällig hierher gekommen, als ich gegoogelt habe.
Mxt3
@ mxt3 Nun, nach meinem Verständnis war es ein Anolog für jede Schleife in Java (funktionierte gleich).
1
@ mxt3 Ich dachte das gleiche! Nachdem ich die akzeptierte Antwort gelesen hatte, fand ich diese Zeile, die mein Problem mit Array.from () löste:Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Antworten:

839

Als Antwort auf die ursprüngliche Frage verwenden Sie for/infalsch. In Ihrem Code keyist der Index. Um den Wert aus dem Pseudo-Array zu erhalten, müssten Sie dies tun, list[key]und um die ID zu erhalten, müssten Sie dies tun list[key].id. Aber Sie sollten dies überhaupt nicht tun for/in.

Zusammenfassung (hinzugefügt im Dezember 2018)

Verwenden Sie for/indiese Option niemals zum Iterieren einer NodeList oder einer HTMLCollection. Die Gründe, dies zu vermeiden, werden unten beschrieben.

Alle neueren Versionen moderner Browser (Safari, Firefox, Chrome, Edge) unterstützen die for/ofIteration in DOM-Listen wie nodeListoder HTMLCollection.

Hier ist ein Beispiel:

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

Um ältere Browser (einschließlich Dinge wie IE) einzuschließen, funktioniert dies überall:

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

Erklärung, warum Sie nicht verwenden sollten for/in

for/inist zum Iterieren der Eigenschaften eines Objekts gedacht. Das heißt, es werden alle iterierbaren Eigenschaften eines Objekts zurückgegeben. Während es für ein Array zu funktionieren scheint (Rückgabe von Array-Elementen oder Pseudo-Array-Elementen), kann es auch andere Eigenschaften des Objekts zurückgeben, die nicht den Erwartungen der Array-ähnlichen Elemente entsprechen. Und raten Sie mal, ein HTMLCollectionodernodeList Objekt kann beide andere Eigenschaften haben, die mit einer for/inIteration zurückgegeben werden. Ich habe dies gerade in Chrome versucht. Wenn Sie es so wiederholen, wie Sie es wiederholt haben, werden die Elemente in der Liste (Indizes 0, 1, 2 usw.) abgerufen, aber auch die Eigenschaften lengthund item. Die for/inIteration funktioniert für eine HTMLCollection einfach nicht.


Unter http://jsfiddle.net/jfriend00/FzZ2H/ erfahren Sie, warum Sie eine HTMLCollection nicht iterieren können for/in.

In Firefox ist Ihr for/in würde Iteration die folgenden Elemente zurückgeben (alle iterierbaren Eigenschaften des Objekts):

0
1
2
item
namedItem
@@iterator
length

Hoffentlich können Sie jetzt sehen, warum Sie for (var i = 0; i < list.length; i++)stattdessen verwenden möchten, damit Sie nur erhalten 0,1 und 2in der Iteration.


Im Folgenden finden Sie eine Entwicklung der Entwicklung der Browser im Zeitraum 2015-2018, die Ihnen zusätzliche Möglichkeiten zur Iteration bietet. Keines davon wird jetzt in modernen Browsern benötigt, da Sie die oben beschriebenen Optionen verwenden können.

Update für ES6 im Jahr 2015

Zu ES6 wurde hinzugefügt, Array.from()dass eine Array-ähnliche Struktur in ein tatsächliches Array konvertiert wird. So kann man eine Liste direkt wie folgt aufzählen:

"use strict";

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

Arbeitsdemo (in Firefox, Chrome und Edge ab April 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Update für ES6 im Jahr 2016

Sie können jetzt das ES6 for / of-Konstrukt mit a NodeListund an verwenden, HTMLCollectionindem Sie dies einfach zu Ihrem Code hinzufügen:

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

Dann können Sie Folgendes tun:

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

Dies funktioniert in der aktuellen Version von Chrome, Firefox und Edge. Dies funktioniert, weil der Array-Iterator sowohl an die NodeList- als auch an die HTMLCollection-Prototypen angehängt wird, sodass beim Array / Iterieren der Array-Iterator verwendet wird, um sie zu iterieren.

Arbeitsdemo: http://jsfiddle.net/jfriend00/joy06u4e/ .


Zweites Update für ES6 im Dezember 2016

Seit Dezember 2016 ist die Symbol.iteratorUnterstützung für Chrome v54 und Firefox v50 integriert, sodass der folgende Code von selbst funktioniert. Es ist noch nicht für Edge integriert.

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

Arbeitsdemo (in Chrome und Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Drittes Update für ES6 im Dezember 2017

Ab Dezember 2017 funktioniert diese Funktion in Edge 41.16299.15.0 für ein nodeListwie in document.querySelectorAll(), aber nicht für ein HTMLCollectionwie in, document.getElementsByClassName()sodass Sie den Iterator manuell zuweisen müssen, um ihn in Edge für ein zu verwenden HTMLCollection. Es ist ein Rätsel, warum sie einen Sammlungstyp reparieren würden, den anderen jedoch nicht. Sie können das Ergebnis von document.querySelectorAll()mit ES6- for/ofSyntax jetzt jedoch zumindest in aktuellen Versionen von Edge verwenden.

Ich habe auch die obige jsFiddle aktualisiert, sodass sie sowohl HTMLCollectionals auch nodeListseparat getestet und die Ausgabe in der jsFiddle selbst erfasst wird.

Viertes Update für ES6 im März 2018

Per Mesqueeeb wurde auch Symbol.iteratorin Safari Unterstützung integriert, sodass Sie diese for (let item of list)entweder für document.getElementsByClassName()oder verwenden können document.querySelectorAll().

Fünftes Update für ES6 im April 2018

Offenbar Unterstützung für eine Iteration HTMLCollectionmit for/ofwird Edge 18 im Herbst 2018 kommen.

Sechstes Update für ES6 im November 2018

Ich kann bestätigen, dass Sie mit Microsoft Edge v18 (das im Windows Update vom Herbst 2018 enthalten ist) jetzt sowohl eine HTMLCollection als auch eine NodeList mit for / of in Edge iterieren können.

Daher enthalten jetzt alle modernen Browser native Unterstützung für die for/ofIteration sowohl der HTMLCollection- als auch der NodeList-Objekte.

jfriend00
quelle
1
Vielen Dank für die sehr detaillierten Updates, die JS aktualisiert hat. Dies hilft Anfängern, diesen Rat im Kontext anderer Artikel / Beiträge zu verstehen, die nur für eine bestimmte JS-Version gelten.
brownmagik352
Hervorragende Antwort, tolle Update-Unterstützung. Danke.
Kosakenmann
79

Sie können for/ inon NodeLists oder HTMLCollections nicht verwenden . Sie können jedoch einige Array.prototypeMethoden verwenden, solange Sie .call()sie verwenden und das NodeListoder HTMLCollectionals übergeben this.

Betrachten Sie Folgendes als Alternative zur Schleife von jfriend00for :

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

Es gibt einen guten Artikel über MDN , der diese Technik behandelt. Beachten Sie jedoch die Warnung zur Browserkompatibilität:

[...] Das Übergeben eines Host-Objekts (wie a NodeList) thisan eine native Methode (wie forEach) funktioniert nicht garantiert in allen Browsern und schlägt in einigen Browsern fehl.

Obwohl dieser Ansatz praktisch ist, kann eine forSchleife die am besten mit dem Browser kompatible Lösung sein.

Update (30. August 2014): Schließlich können Sie ES6 for/of !

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

Es wird bereits in neueren Versionen von Chrome und Firefox unterstützt.

evanrmurphy
quelle
1
Sehr schön! Ich habe diese Technik verwendet, um die Werte ausgewählter Optionen von a zu erhalten <select multiple>. Beispiel:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -
1
Ich habe nach einer ES2015-Lösung für dieses Problem gesucht. Vielen Dank, dass Sie bestätigt haben, dass dies for ... offunktioniert.
Richard Turner
60

In ES6, könnten Sie etwas tun , wie [...collection], oder Array.from(collection),

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

Z.B:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });
mido
quelle
@ DanielM Vermutung, was ich getan habe, ist, flache Klon eine Array-ähnliche Struktur
Mitte
Ich
verstehe
Ich benutze dies immer, viel einfacher für die Augen als Array.from, ich frage mich nur, ob es erhebliche Leistungs- oder Speichernachteile hat. Wenn ich zum Beispiel die Zellen einer Tabellenzeile iterieren muss, verwende ich a [...row.cells].forEachanstelle von arow.querySelectorAll('td')
Mojimi
16

Sie können diese beiden Zeilen hinzufügen:

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

HTMLCollection wird von getElementsByClassName und getElementsByTagName zurückgegeben

NodeList wird von querySelectorAll zurückgegeben

So können Sie eine forEach machen:

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

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

selections.forEach(function(element, i){
//do your stuffs
});
mmnl
quelle
Diese Antwort scheint so effektiv zu sein. Was ist der Haken?
Peheje
2
Der Haken ist, dass diese Lösung unter IE11 nicht funktioniert! Gute Lösung.
Rahul Gaba
2
Beachten Sie, dass NodeListbereits hatforEach() .
Franklin Yu
7

Ich hatte ein Problem mit forEach in IE 11 und auch Firefox 49

Ich habe eine solche Problemumgehung gefunden

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }
Mamosek
quelle
Tolle Lösung für IE11! War früher eine übliche Technik ...
mb21
6

Alternative zu Array.fromist zu verwendenArray.prototype.forEach.call

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

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

ect ...

holmberd
quelle
6

Es gibt keinen Grund, es6-Funktionen zu verwenden, um forSchleifen zu vermeiden, wenn Sie mit IE9 oder höher arbeiten.

In ES5 gibt es zwei gute Optionen. Erstens können Sie "ausleihen" Array, forEachwie Evans erwähnt .

Aber noch besser ...

Verwendung Object.keys(), die nicht haben forEachund Filter auf „eigene Eigenschaften“ automatisch.

Das heißt, Object.keysist im Wesentlichen gleichbedeutend mit einem for... inmit a HasOwnProperty, ist aber viel glatter.

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

Ab März 2016 for...offunktioniert in Chrome 49.0 für HTMLCollection:

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

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

Siehe hier die Dokumentation .

Dies funktioniert jedoch nur, wenn Sie die folgende Problemumgehung anwenden, bevor Sie Folgendes verwenden for...of:

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

Das gleiche ist notwendig für die Verwendung for...ofmit NodeList:

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

Ich glaube / hoffe, dass for...ofes bald ohne die oben genannte Problemumgehung funktionieren wird. Die offene Ausgabe ist hier:

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

Update: Siehe Expenzors Kommentar unten: Dies wurde ab April 2016 behoben. Sie müssen HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator] nicht hinzufügen. über eine HTMLCollection mit for ... of iterieren

MarcG
quelle
4
Dies wurde ab April 2016 behoben. Sie müssen nicht hinzufügen HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];, um über ein HTMLCollectionmit zu iterieren for...of.
Expenzor
3

Am Rande

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);
    }
  }
}
Tiago Pertile
quelle
2

Einfache Problemumgehung, die ich immer benutze

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

Danach können Sie beliebige Array-Methoden für die Auswahl ausführen

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

Wenn Sie ältere ES-Varianten verwenden (z. B. ES5), können Sie Folgendes verwenden as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}
Alon Gouldman
quelle
0

Sie möchten es ändern in

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
}
Andy897
quelle
5
Zu Ihrer Information, siehe meine Antwort, warum dies nicht richtig funktioniert. Das for (key in list)gibt mehrere Eigenschaften von zurück HTMLCollection, die nicht als Elemente in der Sammlung gedacht sind.
jfriend00