Usar forEach em uma matriz de getElementsByClassName resulta em “TypeError: undefined não é uma função”

91

Em meu JSFiddle , estou simplesmente tentando iterar em uma matriz de elementos. O array não está vazio, como provam as declarações de log. No entanto, a chamada para forEachme dá o (não tão útil) erro “Não capturado TypeError: undefinednão é uma função”.

Devo estar fazendo algo estúpido; O que estou fazendo de errado?

Meu código:

var arr = document.getElementsByClassName('myClass');
console.log(arr);
console.log(arr[0]);
arr.forEach(function(v, i, a) {
  console.log(v);
});
.myClass {
  background-color: #FF0000;
}
<div class="myClass">Hello</div>

Jer
fonte
8
arrnão é uma matriz, mas a HTMLCollection. Não possui os mesmos métodos de um array. developer.mozilla.org/en-US/docs/Web/API/… . Aqui está um post sobre isso mesmo: stackoverflow.com/questions/13433799/…
Ian
Algo como [1,2,3].forEach(function(v,i,a) { console.log(v); });está bom. Qual é a diferença entre isso e a matriz no meu exemplo?
Jer
Você não tem uma matriz em seu exemplo. O que te faz pensar que é um array?
Ian
3
@Jer: Como arr instanceof Arrayresultará, falseele não pode se beneficiar de nenhum método de protótipo do Arrayobjeto, como Array.prototype.forEach () . arré um HTMLCollection e um objeto como um array (mas não herda ou instanciado Array). Portanto, seu forloop padrão funcionará como se simplesmente iterasse através do índice do objeto e não fosse um protótipo de Array.
Não
1
@ Jer — você deve examinar as diferenças entre objetos internos e de host. Os primeiros estão em conformidade com a ECMA-262, os últimos apenas na medida em que o host desejar. O DOM possui muitos objetos que permitem o acesso aos membros por índice (document.images, document.forms, form.elements, select.options, etc.), principalmente com base na interface NodeList .
RobG de

Respostas:

162

Isso ocorre porque document.getElementsByClassNameretorna um HTMLCollection , não um array.

Felizmente, é um objeto "semelhante a um array" (o que explica por que é registrado como se fosse um objeto e por que você pode iterar com um forloop padrão ), então você pode fazer isso:

[].forEach.call(document.getElementsByClassName('myClass'), function(v,i,a) {

Com ES6 (em navegadores modernos ou com Babel), você também pode usar o Array.fromque cria arrays a partir de objetos semelhantes a array:

Array.from(document.getElementsByClassName('myClass')).forEach(v=>{

ou espalhe o objeto semelhante a uma matriz em uma matriz:

[...document.getElementsByClassName('myClass'))].forEach(v=>{
Denys Séguret
fonte
2
@Jer argumentsé um. Os objetos jQuery são outro. Você pode fazer um você mesmo:var a = {"0": "str1", "1": "str2", length: 2}
Ian
1
Aqui vamos nós com navegadores antigos novamente ... passar um objeto de host para um método nativo falhará no IE 8 e inferior. Talvez ninguém se importe, mas alguns podem. ;-) Oh, ele também não suporta getElementsByClassName , mas querySelectorAll('.myClass')deve funcionar. Ainda estou esperando que os iteradores sejam adicionados à API NodeList. :-(
RobG de
2
@Jer: Como uma observação lateral, se você pretende sair do loop por qualquer motivo Array.prototype.forEach, não o deixarei fazer isso. Se você tiver esse requisito mais tarde, use o forloop padrão ou se quiser usar o objeto array, use Array.prototype.everyou Array.prototype.some(note que todos / alguns não são suportados no IE8 ou menos)
Não
1
@Ian Você precisa de emenda para que o objeto seja "semelhante a um array". Compare os registros aqui: jsbin.com/sigut/1/edit
Denys Séguret
1
@Ian TBH, a definição de "tipo array" é muito confusa e depende do uso. Às vezes eu não incluir splicenessa definição, mas quando eu quiser ser mais "array-like" de ser capaz de usar map, filtere assim por diante, então eu incluí-lo. O uso de iteração simples forEachnão precisa splice.
Denys Séguret de
11

Experimente isto deve funcionar:

<html>
  <head>
    <style type="text/css">
    </style>
  </head>
  <body>
   <div class="myClass">Hello</div>
   <div class="myClass">Hello</div>

<script type="text/javascript">
    var arr = document.getElementsByClassName('myClass');
    console.log(arr);
    console.log(arr[0]);
    arr = [].slice.call(arr); //I have converted the HTML Collection an array
    arr.forEach(function(v,i,a) {
        console.log(v);
    });
</script>


<style type="text/css">
    .myClass {
    background-color: #FF0000;
}
</style>

  </body>
</html>
Vaibhav Jain
fonte
0

caso deseje acessar o ID de cada elemento de uma classe específica, você pode fazer o seguinte:

    Array.from(document.getElementsByClassName('myClass')).forEach(function(element) {
        console.log(element.id);
    });
Nelles
fonte