Para bucle para elementos de colección HTMLC

406

Estoy tratando de establecer get id de todos los elementos en un HTMLCollectionOf. Escribí el siguiente código:

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

Pero obtuve el siguiente resultado en la consola:

event1
undefined

que no es lo que esperaba ¿Por qué la salida de la segunda consola es la primera salida de undefinedla consola event1?

Neurona
fuente
23
¿Por qué el título dice "foreach" cuando la pregunta es sobre el para ... en la declaración? Vine aquí por accidente cuando busqué en Google.
mxt3
@ mxt3 Bueno, en mi opinión, fue un anólogo de for-each loop en Java (funcionó igual).
1
@ mxt3 ¡Pensé lo mismo! Pero después de leer la respuesta aceptada, encontré esta línea, que resolvió mi problema foreach, usando Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Respuestas:

838

En respuesta a la pregunta original, está utilizando for/inincorrectamente. En su código, keyes el índice. Entonces, para obtener el valor de la pseudo-matriz, tendría que hacer list[key]y para obtener la identificación, lo haría list[key].id. Pero, no deberías estar haciendo esto for/inen primer lugar.

Resumen (agregado en diciembre de 2018)

No lo use nunca for/inpara iterar una lista de nodos o una colección HTMLC. Las razones para evitarlo se describen a continuación.

Todas las versiones recientes de los navegadores modernos (Safari, Firefox, Chrome, Edge) admiten la for/ofiteración en listas DOM como nodeListo HTMLCollection.

Aquí hay un ejemplo:

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

Para incluir navegadores antiguos (incluidos elementos como IE), esto funcionará en todas partes:

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

Explicación de por qué no debe usar for/in

for/inestá destinado a iterar las propiedades de un objeto. Eso significa que devolverá todas las propiedades iterables de un objeto. Si bien puede parecer que funciona para una matriz (elementos de matriz devueltos o elementos de pseudo-matriz), también puede devolver otras propiedades del objeto que no son lo que espera de los elementos tipo matriz. Y, adivina qué, una HTMLCollectiono nodeListobjeto puede tener tanto otras propiedades que serán devueltos con una for/initeración. Acabo de probar esto en Chrome e iterarlo de la forma en que lo hiciste recuperará los elementos de la lista (índices 0, 1, 2, etc.), pero también recuperará las propiedades lengthy item. La for/initeración simplemente no funcionará para una colección HTMLC.


Consulte http://jsfiddle.net/jfriend00/FzZ2H/ para ver por qué no puede iterar una colección HTMLC con for/in.

En Firefox, su for/initeración devolvería estos elementos (todas las propiedades iterables del objeto):

0
1
2
item
namedItem
@@iterator
length

Con suerte, ahora puedes ver por qué quieres usar for (var i = 0; i < list.length; i++)en su lugar, así que solo obtienes 0, 1y 2en tu iteración.


A continuación se muestra una evolución de cómo los navegadores han evolucionado durante el período 2015-2018, ofreciéndole formas adicionales de iterar. Ninguno de estos ahora es necesario en los navegadores modernos, ya que puede usar las opciones descritas anteriormente.

Actualización para ES6 en 2015

Se agregó a ES6 Array.from()que convertirá una estructura tipo matriz en una matriz real. Eso le permite a uno enumerar una lista directamente así:

"use strict";

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

Demostración de trabajo (en Firefox, Chrome y Edge a partir de abril de 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Actualización para ES6 en 2016

Ahora puede usar el ES6 para / of construct con a NodeListy an HTMLCollectionsimplemente agregando esto a su código:

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

Entonces, puedes hacer:

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

Esto funciona en la versión actual de Chrome, Firefox y Edge. Esto funciona porque une el iterador de matriz a los prototipos de NodeList y HTMLCollection para que cuando for / of los itere, use el iterador de matriz para iterarlos.

Demostración de trabajo: http://jsfiddle.net/jfriend00/joy06u4e/ .


Segunda actualización para ES6 en diciembre de 2016

A partir de diciembre de 2016, el Symbol.iteratorsoporte se ha incorporado a Chrome v54 y Firefox v50, por lo que el siguiente código funciona por sí solo. Todavía no está integrado para Edge.

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

Demostración de trabajo (en Chrome y Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Tercera actualización para ES6 en diciembre de 2017

A partir de diciembre de 2017, esta capacidad funciona en Edge 41.16299.15.0 para un nodeListas in document.querySelectorAll(), pero no un HTMLCollectionas in, document.getElementsByClassName()por lo que debe asignar manualmente el iterador para usarlo en Edge para un HTMLCollection. Es un misterio total por qué arreglarían un tipo de colección, pero no el otro. Pero, al menos puede usar el resultado de la sintaxis document.querySelectorAll()ES6 for/ofen las versiones actuales de Edge ahora.

También actualicé el jsFiddle anterior para que pruebe ambos HTMLCollectiony por nodeListseparado y capture la salida en el propio jsFiddle.

Cuarta actualización para ES6 en marzo de 2018

Según mesqueeeb, el Symbol.iteratorsoporte también se ha incorporado a Safari, por lo que puede usarlo for (let item of list)para document.getElementsByClassName()o document.querySelectorAll().

Quinta actualización para ES6 en abril de 2018

Aparentemente, el soporte para iterar un HTMLCollectioncon for/ofllegará a Edge 18 en el otoño de 2018.

Sexta actualización para ES6 en noviembre de 2018

Puedo confirmar que con Microsoft Edge v18 (que se incluye en la actualización de Windows de otoño de 2018), ahora puede iterar tanto una colección HTMLC como una NodeList con for / of en Edge.

Por lo tanto, ahora todos los navegadores modernos contienen soporte nativo para la for/ofiteración de los objetos HTMLCollection y NodeList.

jfriend00
fuente
1
Gracias por las actualizaciones muy detalladas que JS ha actualizado. Esto ayuda a los principiantes a comprender este consejo en el contexto de otros artículos / publicaciones que solo se aplican a una versión específica de JS.
brownmagik352
Excelente respuesta, increíble soporte de actualización ... Gracias.
cosaco
80

No puedes usar for/ inon NodeLists o HTMLCollections. Sin embargo, puede usar algunos Array.prototypemétodos, siempre que .call()los use y pase el NodeListo HTMLCollectioncomo this.

Considere lo siguiente como una alternativa al bucle de jfriend00for :

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

Hay un buen artículo sobre MDN que cubre esta técnica. Sin embargo, tenga en cuenta su advertencia sobre la compatibilidad del navegador:

[...] pasar un objeto host (como a NodeList) thisa un método nativo (como forEach) no se garantiza que funcione en todos los navegadores y se sabe que falla en algunos.

Entonces, si bien este enfoque es conveniente, un forbucle puede ser la solución más compatible con el navegador.

Actualización (30 de agosto de 2014): ¡ Eventualmente podrás usar ES6 for/of !

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

Ya es compatible con versiones recientes de Chrome y Firefox.

evanrmurphy
fuente
1
¡Muy agradable! Utilicé esta técnica para obtener los valores de las opciones seleccionadas de a <select multiple>. Ejemplo:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -
1
Estaba buscando una solución ES2015 para esto, así que gracias por confirmar que for ... offunciona.
Richard Turner
60

En ES6, podrías hacer algo como [...collection], o Array.from(collection),

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

P.ej:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });
mido
fuente
@DanielM supongo que lo que he hecho es, clonar superficialmente una estructura tipo matriz
mido
Ya veo, gracias, ahora he encontrado la documentación que estaba buscando: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM
Siempre uso esto, mucho más fácil para los ojos que Array. Desde allí, me pregunto si tiene un rendimiento considerable o inconvenientes de memoria. Por ejemplo, si necesito iterar las celdas de una fila de la tabla, uso un en [...row.cells].forEachlugar de hacer unrow.querySelectorAll('td')
Mojimi
16

puedes agregar estas dos líneas:

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

HTMLCollection es devuelta por getElementsByClassName y getElementsByTagName

NodeList es devuelto por querySelectorAll

De esta manera puedes hacer un forEach:

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

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

selections.forEach(function(element, i){
//do your stuffs
});
mmnl
fuente
Esta respuesta parece muy efectiva. ¿Cuál es la trampa?
Peheje
2
¡El problema es que esta solución no funciona en IE11! Buena solución sin embargo.
Rahul Gaba
2
Tenga en cuenta que NodeListya tieneforEach() .
Franklin Yu
7

Tuve un problema al usar forEach en IE 11 y también Firefox 49

He encontrado una solución como esta

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }
mamosek
fuente
¡Gran solución para IE11! Solía ser una técnica común ...
MB21
6

Alternativa a Array.fromes usarArray.prototype.forEach.call

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

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

ect ...

holmberd
fuente
6

No hay ninguna razón para usar las funciones de es6 para escapar del forbucle si estás en IE9 o superior.

En ES5, hay dos buenas opciones. En primer lugar, se puede "tomar prestado" Array's forEachcomo Evan menciona .

Pero aún mejor ...

Uso Object.keys(), que no tiene forEachy filtros para "propias propiedades" automáticamente.

Es decir, Object.keyses esencialmente equivalente a hacer un for... incon a HasOwnProperty, pero es mucho más suave.

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

A partir de marzo de 2016, en Chrome 49.0, for...offunciona para HTMLCollection:

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

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

Vea aquí la documentación .

Pero solo funciona si aplica la siguiente solución antes de usar for...of:

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

Lo mismo es necesario para usar for...ofcon NodeList:

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

Creo / espero for...ofque pronto funcione sin la solución anterior. El tema abierto está aquí:

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

Actualización: vea el comentario de Expenzor a continuación: Esto se ha solucionado a partir de abril de 2016. No necesita agregar HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; iterar sobre una colección HTMLC con para ... de

MarcG
fuente
44
Esto se ha solucionado a partir de abril de 2016. No es necesario agregar HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];para iterar sobre un HTMLCollectioncon for...of.
Expenzor
3

Nervioso

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
fuente
2

Solución fácil que siempre uso

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

Después de esto, puede ejecutar cualquier método de matriz deseado en la selección

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

si usa versiones anteriores de ES, (ES5 por ejemplo), puede usar as any:

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

Quieres cambiarlo a

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
fuente
55
Para su información, vea mi respuesta de por qué esto no funcionará correctamente. El for (key in list)devolverá varias propiedades de los HTMLCollectionque no están destinados a ser elementos de la colección.
jfriend00