Loop For para elementos HTMLCollection


406

Estou tentando definir get id de todos os elementos em um HTMLCollectionOf. Eu escrevi o seguinte código:

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

Mas eu consegui a seguinte saída no console:

event1
undefined

o que não é o que eu esperava. Por que a segunda saída undefineddo console é a primeira saída do console event1?


23
por que o título diz "foreach" quando a pergunta é sobre o for ... na declaração? Eu vim aqui por acidente ao pesquisar no Google.
mxt3

@ mxt3 Bem, no meu entendimento, era um anólogo de loop for-each em Java (funcionava da mesma forma).

1
@ mxt3 Eu pensei a mesma coisa! Mas depois de ler a resposta aceita, encontrei esta linha, que resolveu meu problema de foreach, usando Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Respostas:


839

Em resposta à pergunta original, você está usando for/inincorretamente. No seu código, keyé o índice. Portanto, para obter o valor da pseudo-matriz, você teria que fazer list[key]e obter o ID, você faria list[key].id. Mas você não deve fazer isso for/inem primeiro lugar.

Resumo (adicionado em dezembro de 2018)

Nunca use for/inpara iterar um nodeList ou um HTMLCollection. Os motivos para evitá-lo estão descritos abaixo.

Todas as versões recentes de navegadores modernos (Safari, Firefox, Chrome, Edge) todo o apoio for/ofiteração no DOM lista tais nodeListou HTMLCollection.

Aqui está um exemplo:

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

Para incluir navegadores mais antigos (incluindo coisas como o IE), isso funcionará em qualquer lugar:

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

Explicação sobre por que você não deve usar for/in

for/indestina-se a iterar as propriedades de um objeto. Isso significa que ele retornará todas as propriedades iteráveis ​​de um objeto. Embora pareça funcionar para uma matriz (retornando elementos da matriz ou elementos de pseudo-matriz), também pode retornar outras propriedades do objeto que não são o que você espera dos elementos semelhantes à matriz. E, adivinhe, um objeto HTMLCollectionou nodeListpode ter outras propriedades que serão retornadas com uma for/initeração. Eu apenas tentei isso no Chrome e a iteração da maneira que você estava iterando recuperará os itens da lista (índices 0, 1, 2, etc ...), mas também recuperará as propriedades lengthe item. A for/initeração simplesmente não funcionará para uma HTMLCollection.


Veja http://jsfiddle.net/jfriend00/FzZ2H/ para saber por que você não pode iterar uma HTMLCollection for/in.

No Firefox, sua for/initeração retornaria esses itens (todas as propriedades iteráveis ​​do objeto):

0
1
2
item
namedItem
@@iterator
length

Felizmente, agora você pode ver por que você deseja usar for (var i = 0; i < list.length; i++)em vez de modo que você acabou de começar 0, 1e 2em sua iteração.


A seguir, é apresentada uma evolução de como os navegadores evoluíram ao longo do período 2015-2018, oferecendo maneiras adicionais de iterar. Agora nada disso é necessário em navegadores modernos, pois você pode usar as opções descritas acima.

Atualização para ES6 em 2015

Adicionado ao ES6 é Array.from()que ele converterá uma estrutura semelhante a uma matriz em uma matriz real. Isso permite enumerar uma lista diretamente assim:

"use strict";

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

Demonstração de trabalho (no Firefox, Chrome e Edge em abril de 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Atualização para ES6 em 2016

Agora você pode usar o ES6 para / de construção com um NodeListe um HTMLCollectionpor apenas adicionando isso ao seu código:

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

Então, você pode fazer:

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

Isso funciona na versão atual do Chrome, Firefox e Edge. Isso funciona porque anexa o iterador Array aos protótipos NodeList e HTMLCollection, para que, quando for iterado por / deles, use o iterador Array para iterá-los.

Demonstração de trabalho: http://jsfiddle.net/jfriend00/joy06u4e/ .


Segunda atualização do ES6 em dezembro de 2016

Desde dezembro de 2016, o Symbol.iteratorsuporte foi incorporado ao Chrome v54 e Firefox v50, portanto, o código abaixo funciona por si só. Ainda não está embutido no Edge.

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

Demonstração de trabalho (no Chrome e Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Terceira atualização para ES6 em dezembro de 2017

A partir de dezembro de 2017, esse recurso funcionaria no Edge 41.16299.15.0 para a nodeListcomo em document.querySelectorAll(), mas não para HTMLCollectioncomo, document.getElementsByClassName()portanto, você deve atribuir manualmente o iterador para usá-lo no Edge para um HTMLCollection. É um mistério total o motivo pelo qual eles consertam um tipo de coleção, mas não o outro. Porém, você pode pelo menos usar o resultado da sintaxe document.querySelectorAll()ES6 for/ofnas versões atuais do Edge agora.

Também atualizei o jsFiddle acima para que ele teste ambos HTMLCollectione nodeListseparadamente e capture a saída no próprio jsFiddle.

Quarta atualização do ES6 em março de 2018

Por mesqueeeb, o Symbol.iteratorsuporte também foi incorporado ao Safari, para que você possa usar for (let item of list)um document.getElementsByClassName()ou outro document.querySelectorAll().

Quinta atualização para ES6 em abril de 2018

Aparentemente, o suporte para iterar um HTMLCollectioncom for/ofchegará ao Edge 18 no outono de 2018.

Sexta atualização para ES6 em novembro de 2018

Posso confirmar que, com o Microsoft Edge v18 (incluído no Windows Update de outono de 2018), agora você pode iterar um HTMLCollection e um NodeList com for / of no Edge.

Portanto, agora todos os navegadores modernos contêm suporte nativo para for/ofiteração dos objetos HTMLCollection e NodeList.


1
Obrigado pelas atualizações muito detalhadas, como o JS atualizou. Isso ajuda os iniciantes a entender esse conselho no contexto de outros artigos / postagens que se aplicam apenas a uma versão JS específica.
brownmagik352

Resposta excelente, suporte incrível a atualizações. Obrigado.
cossacksman

79

Você não pode usar for/ inon NodeLists ou HTMLCollections. No entanto, você pode usar alguns Array.prototypemétodos, desde que você .call()os use e passe no NodeListou HTMLCollectioncomo this.

Portanto, considere o seguinte como uma alternativa ao loop de jfriend00for :

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

Há um bom artigo sobre o MDN que aborda essa técnica. Observe o aviso sobre a compatibilidade do navegador:

[...] passar um objeto host (como a NodeList) thispara um método nativo (como forEach) não é garantido que funcione em todos os navegadores e é conhecido por falhar em alguns.

Portanto, embora essa abordagem seja conveniente, um forloop pode ser a solução mais compatível com o navegador.

Atualização (30 de agosto de 2014): Eventualmente, você poderá usar o ES6 for/of !

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

Já é suportado em versões recentes do Chrome e Firefox.


1
Muito agradável! Eu usei essa técnica para obter os valores das opções selecionadas de a <select multiple>. Exemplo:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I - 01/04/2015

1
Eu estava procurando por uma solução ES2015 para isso, então obrigado por confirmar que for ... offunciona.
Richard Turner

60

No ES6, você poderia fazer algo como [...collection], ou Array.from(collection),

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

Por exemplo:-

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

@DanielM acho que o que eu fiz é clonar superficialmente uma estrutura semelhante a uma matriz #
mido 27/10

Entendo, obrigado - agora encontrei a documentação que procurava: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM 27/10/16

Eu sempre uso isso, muito mais fácil para os olhos do que Array. De, eu apenas me pergunto se ele tem um desempenho considerável ou desvantagens de memória. Por exemplo, se eu precisar iterate células de uma linha da tabela Eu uso uma [...row.cells].forEachvez de fazer umrow.querySelectorAll('td')
Mojimi

16

você pode adicionar estas duas linhas:

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

HTMLCollection é retornado por getElementsByClassName e getElementsByTagName

NodeList é retornado por querySelectorAll

Assim você pode fazer um forEach:

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

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

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

Esta resposta parece tão eficaz. Qual é o problema?
Peheje 26/04

2
O problema é que esta solução não funciona no IE11! Boa solução embora.
Rahul Gaba


7

Ocorreu um problema ao usar o forEach no IE 11 e também o Firefox 49

Eu encontrei uma solução alternativa como esta

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

Ótima solução para o IE11! Costumava ser uma técnica comum ...
MB21

6

Alternativa para Array.fromé 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 ...


6

Não há razão para usar os recursos es6 para escapar do forloop se você estiver no IE9 ou superior.

No ES5, existem duas boas opções. Primeiro, você pode "emprestar" Arrayé forEachcomo evan menciona .

Mas melhor ainda ...

Use Object.keys(), que não têm forEache filtros para "próprias propriedades" automaticamente.

Ou seja, Object.keysé essencialmente equivalente a fazer a for... incom a HasOwnProperty, mas é muito mais suave.

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

5

Em março de 2016, no Chrome 49.0, for...oftrabalha para HTMLCollection:

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

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

Veja aqui a documentação .

Mas isso só funciona se você aplicar a seguinte solução alternativa antes de usar o for...of:

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

O mesmo é necessário para usar for...ofcom NodeList:

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

Acredito / espero for...ofque funcione em breve sem a solução alternativa acima. A questão em aberto está aqui:

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

Atualização: Veja o comentário do Expenzor abaixo: Isso foi corrigido em abril de 2016. Você não precisa adicionar HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; para iterar sobre uma HTMLCollection com for ... of


4
Isso foi corrigido em abril de 2016. Você não precisa adicionar HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];para iterar ao longo de um HTMLCollectioncom for...of.
Expenzor

3

No limite

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

Solução fácil que eu sempre uso

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

Depois disso, você pode executar qualquer método de matriz desejado na seleção

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

1

se você usar versões mais antigas do ES (ES5, por exemplo), poderá usar as any:

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

0

Você deseja alterá-lo para

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
Para sua informação, veja minha resposta para saber por que isso não funcionará corretamente. O for (key in list)retornará várias propriedades do HTMLCollectionque não devem ser itens na coleção.
jfriend00
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.