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.