Por que o nodelist não tem forEach?

91

Eu estava trabalhando em um pequeno script para alterar <abbr>o texto interno dos elementos, mas descobri que nodelistnão tem um forEachmétodo. Eu sei que nodelistnão herda de Array, mas não parece que forEachseria um método útil? Existe um problema de implementação particular eu não tenho conhecimento de que impede a adição forEachde nodelist?

Nota: Estou ciente de que o Dojo e o jQuery têm forEach, de alguma forma, suas listas de nós. Não posso usar nenhum deles devido a limitações.

Cobras e Café
fonte
6
Olá do futuro! nodeList tem forEach desde ES6 .
Blaise de

Respostas:

94

NodeList agora tem forEach () em todos os principais navegadores

Consulte nodeList forEach () em MDN .

Resposta original

Nenhuma dessas respostas explica por que NodeList não herda de Array, permitindo que ele tenha forEache todo o resto.

A resposta é encontrada neste tópico es-discuss . Resumindo, ele quebra a web:

O problema era o código que assumia incorretamente que instanceof significava que a instância era um Array em combinação com Array.prototype.concat.

Havia um bug no Closure Library do Google que fazia com que quase todos os aplicativos do Google falhassem devido a isso. A biblioteca foi atualizada assim que foi encontrada, mas ainda pode haver código por aí que faz a mesma suposição incorreta em combinação com concat.

Ou seja, algum código fez algo como

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

No entanto, concattratará arrays "reais" (não instância de Array) de maneira diferente de outros objetos:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

então isso significa que o código acima quebrou quando xera um NodeList, porque antes ele desceu o doSomethingElseWith(x)caminho, enquanto depois ele desceu o otherArray.concat(x)caminho, o que fez algo estranho, já que xnão era um array real.

Há algum tempo surgiu a proposta de uma Elementsclasse que fosse uma verdadeira subclasse de Array, e seria usada como "a nova NodeList". No entanto, isso foi removido do padrão DOM , pelo menos por enquanto, uma vez que ainda não era viável de implementar por uma variedade de razões técnicas e de especificação.

Domenic
fonte
11
Parece uma má chamada para mim. Sério, acho que é a decisão certa quebrar coisas de vez em quando, especialmente se isso significa que temos APIs sãs para o futuro. Além disso, não é como se a web estivesse nem perto de ser uma plataforma estável, as pessoas estão acostumadas com seu javascript de 2 anos não funcionando mais como esperado ..
JoyalToTheWorld
3
"Breaks the web"! = "Breaks the Google stuff"
Matt,
Por que não apenas pegar emprestado o método forEach de Array.prototype? Por exemplo, em vez de adicionar Array como protótipo, apenas faça this.forEach = Array.prototype.forEach no construtor ou apenas implemente forEach exclusivamente para NodeList?
PopKernel
1
Nota; esta atualização NodeList now has forEach() in all major browsersparece implicar que o IE não é um navegador importante. Espero que isso seja verdade para algumas pessoas, mas não é verdade para mim (ainda).
Graham P Heath
58

Você pode fazer

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );
Akuhn
fonte
3
Array.prototype.forEach.callpode ser abreviado para[].forEach.call
CodeBrauer
7
@CodeBrauer: isso não é apenas encurtamento Array.prototype.forEach.call, é criar um array vazio e usar seu forEachmétodo.
Paul D. Waite de
34

Você pode considerar a criação de uma nova matriz de nós.

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

Nota: Esta é apenas uma lista / array de referências de nós que estamos criando aqui, sem nós duplicados.

  nodes[0] === nodeList[0] // will be true
sbr
fonte
22
Ou apenas faça Array.prototype.forEach.call(nodeList, fun).
akuhn
Também gostaria de sugerir aliasing a função forEach tal que: var forEach = Array.prototype.forEach.call(nodeList, callback);. Agora você pode simplesmente ligarforEach(nodeList, callback);
Andrew Craswell
19

Nunca diga nunca, estamos em 2016 e o NodeListobjeto implementou um forEachmétodo no Chrome mais recente (v52.0.2743.116).

É muito cedo para usá-lo em produção porque outro navegador não suporta isso ainda (FF 49 testado), mas acho que isso será padronizado em breve.

maioman
fonte
2
Opera também oferece suporte a ele e o suporte será adicionado na versão 50 do Firefox, com lançamento previsto para 15/11/16.
Salsicha
1
Embora implementado, não faz parte de nenhum padrão. Ainda é melhor fazer o Array.prototype.slice.call(nodelist).forEach(…)que é padrão e funciona em navegadores antigos.
Nate de
17

Em suma, é um conflito de design para implementar esse método.

Do MDN:

Por que não posso usar forEach ou map em uma NodeList?

NodeList são usados ​​de forma muito parecida com arrays e seria tentador usar métodos Array.prototype neles. No entanto, isso é impossível.

JavaScript possui um mecanismo de herança baseado em protótipos. As instâncias de array herdam métodos de array (como forEach ou map) porque sua cadeia de protótipo se parece com o seguinte:

myArray --> Array.prototype --> Object.prototype --> null (a cadeia de protótipo de um objeto pode ser obtida chamando várias vezes Object.getPrototypeOf)

forEach, map e similares são propriedades próprias do objeto Array.prototype.

Ao contrário dos arrays, a cadeia de protótipos NodeList se parece com o seguinte:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype contém o método do item, mas nenhum dos métodos Array.prototype, portanto, eles não podem ser usados ​​em NodeLists.

Fonte: https://developer.mozilla.org/en-US/docs/DOM/NodeList (role para baixo até Por que não posso usar forEach ou mapear em um NodeList? )

Matt Lo
fonte
8
Então, já que é uma lista, por que foi projetada dessa forma? O que os estava impedindo de fazer corrente myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null:?
Igor Pantović
14

Se você quiser usar forEach em NodeList, apenas copie essa função do Array:

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

Isso é tudo, agora você pode usá-lo da mesma maneira que faria para Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
AlexTR
fonte
4
Exceto que a mutação das classes básicas não é muito explícita para o leitor médio. Em outras palavras, profundamente em algum código, você deve se lembrar de cada personalização que fizer nos objetos base do navegador. Contar com a documentação MDN não é mais útil porque os objetos mudaram o comportamento da norma. É melhor aplicar explicitamente o protótipo no momento da chamada para que o leitor possa perceber facilmente que forEach é uma ideia emprestada e não algo que faz parte da definição da linguagem. Veja a resposta que @akuhn tem acima.
Sukima
@Sukima desencaminhado por premissas erradas. Neste caso específico, determinada abordagem corrige o NodeList para se comportar conforme o esperado pelo desenvolvedor. Esta é a maneira mais adequada de corrigir o problema de classe do sistema. (NodeList parece estar inacabado e deve ser corrigido em versões futuras do idioma.)
Daniel Garmoshka
1
agora que NodeList.forEach existe, ele se torna um polyfill hilariante simples!
Damon
7

No ES2015, agora você pode usar o forEachmétodo para o nodeList.

document.querySelectorAll('abbr').forEach( el => console.log(el));

Veja o link MDN

No entanto, se você quiser usar coleções HTML ou outros objetos semelhantes a array, em es2015, você pode usar o Array.from()método. Este método pega um objeto semelhante a um array ou iterável (incluindo nodeList, coleções HTML, strings, etc.) e retorna uma nova instância de Array. Você pode usá-lo assim:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

Como o Array.from()método é shimmable, você pode usá-lo em código es5 como este

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

Para obter detalhes, consulte a página MDN .

Para verificar o suporte do navegador atual .

OU

outra maneira es2015 é usar o operador de propagação.

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

Operador de spread MDN

Spread Operator - Suporte de navegador

Mishel Tanvir Habib
fonte
2

Minha solução:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Bakos Bence
fonte
1
Normalmente, não é uma boa ideia estender a funcionalidade do DOM por meio de protótipos, especialmente em versões mais antigas do IE ( artigo ).
KFE
0

NodeList faz parte da API DOM. Observe as ligações ECMAScript que também se aplicam ao JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . O nodeList e uma propriedade de comprimento somente leitura e função de item (índice) para retornar um nó.

A resposta é: você precisa iterar. Não ha alternativa. Foreach não funcionará. Eu trabalho com ligações Java DOM API e tenho o mesmo problema.

randominstanceOfLivingThing
fonte
Mas há uma razão particular pela qual não deve ser implementado? Tanto o jQuery quanto o Dojo o implementaram em suas próprias bibliotecas
Snakes and Coffee
2
mas como isso é um conflito de design?
Snakes and Coffee
0

Verifique o MDN para obter a especificação NodeList.forEach .

NodeList.forEach(function(item, index, nodeList) {
    // code block here
});

No IE, use a resposta de akuhn :

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
VesperX
fonte