El uso de forEach en una matriz de getElementsByClassName da como resultado "TypeError: undefined no es una función"

91

En mi JSFiddle , simplemente estoy tratando de iterar sobre una matriz de elementos. La matriz no está vacía, como demuestran las declaraciones de registro. Sin embargo, la llamada a forEachme da el (no tan útil) error "No detectado TypeError: undefinedno es una función".

Debo estar haciendo algo estúpido; ¿Qué estoy haciendo mal?

Mi 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
fuente
8
arrno es una matriz, sino una HTMLCollection. No tiene los mismos métodos que una matriz. developer.mozilla.org/en-US/docs/Web/API/… . Aquí hay una publicación SO incluso al respecto: stackoverflow.com/questions/13433799/…
Ian
Algo como [1,2,3].forEach(function(v,i,a) { console.log(v); });está bien. ¿Cuál es la diferencia entre esto y la matriz en mi ejemplo?
Jer
Usted no tiene una gran variedad en su ejemplo. ¿Qué te hace pensar que es una matriz?
Ian
3
@Jer: Como arr instanceof Arrayresultado, falseno podrá utilizar ningún método prototipo del Arrayobjeto, como Array.prototype.forEach () . arres una HTMLCollection y una matriz como un objeto (pero no hereda ni crea una instancia Array). Por lo tanto, su forciclo estándar funcionará ya que simplemente itera a través del índice del objeto y no es un prototipo de Array.
No
1
@ Jer: debería analizar las diferencias entre los objetos integrados y los host. El primero se ajusta a ECMA-262, el último solo en la medida en que lo desee el anfitrión. El DOM tiene muchos objetos que permiten el acceso a los miembros por índice (document.images, document.forms, form.elements, select.options, etc.), principalmente basados ​​en la interfaz NodeList .
RobG

Respuestas:

162

Eso es porque document.getElementsByClassNamedevuelve una colección HTMLCollection , no una matriz.

Afortunadamente, es un objeto "similar a una matriz" (lo que explica por qué se registra como si fuera un objeto y por qué puede iterar con un forbucle estándar ), por lo que puede hacer esto:

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

Con ES6 (en navegadores modernos o con Babel), también puede usar Array.fromque crea matrices a partir de objetos similares a matrices:

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

o distribuir el objeto similar a una matriz en una matriz:

[...document.getElementsByClassName('myClass'))].forEach(v=>{
Denys Séguret
fuente
2
@Jer argumentses uno. Los objetos jQuery son otro. Podrías hacer uno tú mismo:var a = {"0": "str1", "1": "str2", length: 2}
Ian
1
Aquí vamos con los navegadores antiguos de nuevo ... pasar un objeto host a un método nativo fallará en IE 8 y versiones anteriores. Quizás a nadie le importe, pero a algunos sí. ;-) Oh, tampoco es compatible con getElementsByClassName , pero querySelectorAll('.myClass')debería funcionar. Todavía estoy esperando que se agreguen iteradores a la API NodeList. :-(
RobG
2
@Jer: Como nota al margen, si tiene la intención de salir del ciclo por cualquier motivo Array.prototype.forEach, no le permitirá hacerlo. Si tiene ese requisito, use el forbucle estándar más adelante o si desea usar el objeto de matriz, use Array.prototype.everyo Array.prototype.some(tenga en cuenta que todos / algunos no son compatibles con IE8 o menos)
No.
1
@Ian Necesita empalmar para que el objeto sea "similar a una matriz". Compare los registros aquí: jsbin.com/sigut/1/edit
Denys Séguret
1
@Ian TBH la definición de "tipo matriz" es muy difusa y depende del uso. A veces no lo incluyo spliceen esa definición, pero cuando quiero ser más "como una matriz" para poder usar map, filteretc., entonces lo incluyo. forEachNo es necesario el uso de iteraciones simples splice.
Denys Séguret
11

Prueba esto, debería 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
fuente
0

en el caso de que desee acceder al ID de cada elemento de una clase específica puede hacer lo siguiente:

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