Per loop per gli elementi HTMLCollection


406

Sto cercando di impostare get id di tutti gli elementi in un HTMLCollectionOf. Ho scritto il seguente codice:

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

Ma ho ottenuto il seguente output in console:

event1
undefined

che non è quello che mi aspettavo. Perché l'output della seconda console è l'output undefineddella prima console event1?


23
perché il titolo dice "foreach" quando la domanda riguarda l'affermazione for ... in? Sono venuto qui per caso quando ho cercato su Google.
mxt3,

@ mxt3 Beh, a mio avviso, era un anologo del ciclo for-each in Java (funzionava allo stesso modo).

1
@ mxt3 Ho pensato la stessa cosa! Ma dopo aver letto la risposta accettata, ho trovato questa riga, che ha risolto il mio problema foreach, usando Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Risposte:


838

In risposta alla domanda originale, si sta utilizzando in for/inmodo errato. Nel tuo codice, keyè l'indice. Quindi, per ottenere il valore dallo pseudo-array, dovresti fare list[key]e ottenere l'id, lo faresti list[key].id. Ma non dovresti farlo for/inin primo luogo.

Riepilogo (aggiunto a dicembre 2018)

Non usare mai for/inper iterare un nodeList o un HTMLCollection. I motivi per evitarlo sono descritti di seguito.

Tutte le versioni recenti dei browser moderni (Safari, Firefox, Chrome, Edge) supportano tutte l' for/ofiterazione su elenchi DOM come nodeListo HTMLCollection.

Ecco un esempio:

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

Per includere browser meno recenti (inclusi elementi come IE), funzionerà ovunque:

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

Spiegazione del perché non si dovrebbe usare for/in

for/inè pensato per iterare le proprietà di un oggetto. Ciò significa che restituirà tutte le proprietà iterabili di un oggetto. Mentre può sembrare funzionare per un array (restituendo elementi array o pseudo-array), può anche restituire altre proprietà dell'oggetto che non sono quelle che ti aspetti dagli elementi simili a array. E, indovina, un oggetto HTMLCollectiono nodeListpossono entrambi avere altre proprietà che verranno restituite con for/inun'iterazione. Ho appena provato questo in Chrome e iterandolo nel modo in cui lo stavi iterando recupererà gli elementi nell'elenco (indici 0, 1, 2, ecc ...), ma recupererà anche le proprietà lengthe item. L' for/initerazione semplicemente non funzionerà per un HTMLCollection.


Vedi http://jsfiddle.net/jfriend00/FzZ2H/ per i motivi per cui non puoi ripetere una raccolta HTMLC for/in.

In Firefox, la tua for/initerazione restituirebbe questi elementi (tutte le proprietà iterabili dell'oggetto):

0
1
2
item
namedItem
@@iterator
length

Si spera, ora si può vedere perché si desidera utilizzare for (var i = 0; i < list.length; i++)invece così basta avere 0, 1e 2nella vostra iterazione.


Di seguito è riportata un'evoluzione del modo in cui i browser si sono evoluti nel periodo 2015-2018 offrendo ulteriori modi per iterare. Nessuno di questi è ora necessario nei browser moderni poiché è possibile utilizzare le opzioni sopra descritte.

Aggiornamento per ES6 nel 2015

Aggiunto a ES6 è Array.from()che convertirà una struttura simile ad un array in un array reale. Ciò consente di enumerare un elenco direttamente come questo:

"use strict";

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

Demo funzionante (in Firefox, Chrome e Edge a partire da aprile 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Aggiornamento per ES6 nel 2016

Ora puoi usare ES6 per / of construct con a NodeListe an HTMLCollectionsemplicemente aggiungendo questo al tuo codice:

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

Quindi, puoi fare:

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

Funziona con la versione corrente di Chrome, Firefox e Edge. Questo funziona perché collega l'iteratore di array a entrambi i prototipi NodeList e HTMLCollection in modo che, quando per / di iterali, utilizza l'iteratore di array per iterarli.

Demo funzionante: http://jsfiddle.net/jfriend00/joy06u4e/ .


Secondo aggiornamento per ES6 nel dicembre 2016

A partire da dicembre 2016, il Symbol.iteratorsupporto è stato integrato in Chrome v54 e Firefox v50, quindi il codice seguente funziona da solo. Non è ancora integrato per Edge.

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

Demo funzionante (in Chrome e Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Terzo aggiornamento per ES6 nel dicembre 2017

A partire da dicembre 2017, questa funzionalità funziona in Edge 41.16299.15.0 per un nodeListcome in document.querySelectorAll(), ma non HTMLCollectioncome in, document.getElementsByClassName()quindi è necessario assegnare manualmente l'iteratore per usarlo in Edge per un HTMLCollection. È un mistero totale il motivo per cui avrebbero corretto un tipo di raccolta, ma non l'altro. Ma ora puoi almeno usare il risultato di document.querySelectorAll()con la for/ofsintassi ES6 nelle versioni attuali di Edge.

Ho anche aggiornato il sopra jsFiddle in modo che controlla sia HTMLCollectione nodeListseparatamente e cattura l'uscita nel jsFiddle stesso.

Quarto aggiornamento per ES6 a marzo 2018

Per mesqueeeb, il Symbol.iteratorsupporto è stato integrato anche in Safari, quindi puoi usarlo for (let item of list)per document.getElementsByClassName()o document.querySelectorAll().

Quinto aggiornamento per ES6 nell'aprile 2018

Apparentemente, il supporto per iterare un HTMLCollectioncon for/ofarriverà a Edge 18 nell'autunno 2018.

Sesto aggiornamento per ES6 nel novembre 2018

Posso confermare che con Microsoft Edge v18 (incluso in Windows Update dell'autunno 2018), è ora possibile iterare sia un HTMLCollection che un NodeList con for / of in Edge.

Quindi, ora tutti i browser moderni contengono supporto nativo per l' for/ofiterazione degli oggetti HTMLCollection e NodeList.


1
Grazie per gli aggiornamenti molto dettagliati come JS ha aggiornato. Questo aiuta i principianti a comprendere questo consiglio nel contesto di altri articoli / post che si applicano solo a una versione JS specifica.
brownmagik352

Risposta eccezionale, supporto straordinario per gli aggiornamenti .. Grazie.
cossacksman

80

Non puoi usare for/ insu NodeLists o HTMLCollections. Tuttavia, è possibile utilizzare alcuni Array.prototypemetodi, purché .call()siano disponibili e passare in NodeListo HTMLCollectionas this.

Quindi considera quanto segue come alternativa al loop di jfriend00for :

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

C'è un buon articolo su MDN che tratta questa tecnica. Prendi nota dei loro avvertimenti sulla compatibilità del browser:

[...] il passaggio di un oggetto host (come a NodeList) thisa un metodo nativo (come forEach) non è garantito per funzionare in tutti i browser ed è noto che fallisce in alcuni.

Quindi, sebbene questo approccio sia conveniente, un forloop potrebbe essere la soluzione più compatibile con il browser.

Aggiornamento (30 agosto 2014): Alla fine sarai in grado di utilizzare ES6 for/of !

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

È già supportato nelle ultime versioni di Chrome e Firefox.


1
Molto bella! Ho usato questa tecnica per ottenere i valori delle opzioni selezionate da a <select multiple>. Esempio:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -

1
Stavo cercando una soluzione ES2015 a questo, quindi grazie per aver confermato che for ... offunziona.
Richard Turner,

60

In ES6, potresti fare qualcosa come [...collection], o Array.from(collection),

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

Per esempio:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });

@DanielM indovina cosa ho fatto, clonare in modo superficiale una struttura simile ad un array
mido

Vedo, grazie - ora ho trovato la documentazione che stavo cercando: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM

Lo uso sempre, molto più facile per gli occhi di Array.da, mi chiedo solo se ha notevoli svantaggi di prestazioni o memoria. Ad esempio, se devo iterare le celle di una riga di tabella, utilizzo [...row.cells].forEachinvece di fare unrow.querySelectorAll('td')
Mojimi

16

puoi aggiungere queste due righe:

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

HTMLCollection viene restituito da getElementsByClassName e getElementsByTagName

NodeList viene restituito da querySelectorAll

In questo modo puoi fare un forOach:

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

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

selections.forEach(function(element, i){
//do your stuffs
});

Questa risposta sembra così efficace. Qual è il trucco?
Peheje,

2
Il problema è che questa soluzione non funziona su IE11! Buona soluzione però.
Rahul Gaba,


7

Ho avuto un problema usando forEach in IE 11 e anche Firefox 49

Ho trovato una soluzione alternativa come questa

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }

Ottima soluzione per IE11! Utilizzato per essere una tecnica comune ...
mb21

6

L'alternativa Array.fromè usareArray.prototype.forEach.call

per ciascuno: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

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

ect ...


6

Non c'è motivo di utilizzare le funzionalità es6 per evitare il forlooping se si utilizza IE9 o versioni successive.

In ES5, ci sono due buone opzioni. In primo luogo, si può "prendere in prestito" Array's forEachcome Evan menziona .

Ma ancora meglio ...

Usa Object.keys(), che non hanno forEache filtri per "proprie proprietà" automaticamente.

Cioè, Object.keysè essenzialmente equivalente a fare un for... incon un HasOwnProperty, ma è molto più fluido.

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

5

A partire da marzo 2016, in Chrome 49.0, for...offunziona per HTMLCollection:

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

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

Vedi qui la documentazione .

Funziona solo se si applica la seguente soluzione alternativa prima di utilizzare il for...of:

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

Lo stesso è necessario per l'utilizzo for...ofcon NodeList:

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

Credo / spero for...ofche presto funzionerà senza la soluzione sopra menzionata. Il problema aperto è qui:

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

Aggiornamento: vedere il commento di Expenzor di seguito: è stato risolto ad aprile 2016. Non è necessario aggiungere HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; per scorrere su HTMLCollection con for ... of


4
Questo problema è stato risolto a partire da aprile 2016. Non è necessario aggiungere HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];l'iterazione su un HTMLCollectioncon for...of.
Expenzor

3

Sul bordo

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

2

Soluzione semplice che uso sempre

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

Successivamente, è possibile eseguire tutti i metodi dell'array desiderati sulla selezione

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

1

se usi varsioni di ES precedenti, (ES5 ad esempio), puoi usare as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}

0

Vuoi cambiarlo 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
}

5
Cordiali saluti, vedere la mia risposta sul perché questo non funziona correttamente. Il for (key in list)torneranno diverse proprietà del HTMLCollectionche non sono destinate ad essere gli elementi della collezione.
jfriend00
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.