JS: iteración sobre el resultado de getElementsByClassName usando Array.forEach

240

Quiero iterar sobre algunos elementos DOM, estoy haciendo esto:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

pero me sale un error:

document.getElementsByClassName ("myclass"). forEach no es una función

Estoy usando Firefox 3, así que sé que tanto getElementsByClassNamey Array.forEachestán presentes. Esto funciona bien:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

¿Es el resultado de getElementsByClassNameuna matriz? Si no, ¿qué es?

Steve Claridge
fuente

Respuestas:

384

No. Como se especifica en DOM4 , es un HTMLCollection(al menos en los navegadores modernos. Los navegadores más antiguos devolvieron unNodeList ).

En todos los navegadores modernos (prácticamente cualquier otro IE <= 8), puede llamar al forEachmétodo Array , pasándole la lista de elementos (ya sea HTMLCollectiono NodeList) como el thisvalor:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

Si está en la feliz posición de poder usar ES6 (es decir, puede ignorar Internet Explorer de manera segura o está usando un transpilador ES5), puede usar Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Tim Down
fuente
29
No es necesario convertirlo primero en una matriz. Solo úsalo [].forEach.call(elsArray, function () {...}).
kay - SE es malvado
1
NO es una NodeList. Es un objeto tipo matriz. Ni siquiera creo que tenga un tipo de instancia. querySelectorAllSin embargo, el método devuelve una NodeList.
Maksim Vi.
2
@MaksimVi. Tiene toda la razón: DOM4 especifica que document.getElementsByClassName()debe devolver un HTMLCollection(que es muy similar pero no una NodeList). Gracias por señalar el error.
Tim Down
@MaksimVi .: Me pregunto si eso cambió en algún momento. Usualmente reviso estas cosas.
Tim Down
1
@TimDown, gracias por la HTMLCollectionsugerencia. Ahora finalmente puedo usar HTMLCollection.prototype.forEach = Array.prototype.forEach;en mi código.
Maksim Vi.
70

Puede usar Array.frompara convertir la colección en matriz, que es mucho más limpia que Array.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

En los navegadores más antiguos que no son compatibles Array.from, debe usar algo como Babel.


ES6 también agrega esta sintaxis:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

Descanse la desestructuración con ...trabajos en todos los objetos similares a una matriz, no solo las matrices en sí mismas, sino que se usa una buena sintaxis de matriz antigua para construir una matriz a partir de los valores.


Mientras que la función alternativa querySelectorAll(que hace getElementsByClassNameobsoleta) devuelve una colección que tiene forEachnativamente, otros métodos como mapo filterfaltan, por lo que esta sintaxis sigue siendo útil:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Athari
fuente
66
Nota: sin transpirar como se sugiere (Babel), esto NO es compatible en IE <Edge, Opera, Safari <9, navegador de Android, Chrome para Android, ... etc.) Fuente: documentos de desarrollo de mozilla
Sean
30

O puede usar querySelectorAllque devuelve NodeList :

document.querySelectorAll('.myclass').forEach(...)

Compatible con navegadores modernos (incluido Edge, pero no IE):
¿Puedo usar querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()

icl7126
fuente
44
Tenga en cuenta la penalización de rendimiento sobre getElementByClassName
Szabolcs Páll
3
La penalización de rendimiento es insignificante en comparación con otras tareas más intensivas como modificar DOM. Si ejecuto 60,000 de estos en 1 milisegundo , estoy bastante seguro de que no será un problema para un uso razonable :)
icl7126
1
Vinculaste el punto de referencia equivocado. Aquí está la correcta medida that.net/Benchmarks/Show/4076/0/… Simplemente lo ejecuté en mi teléfono de gama baja, obtuve 160k / s frente a 380k / s. Como mencionó la manipulación del DOM, aquí también está eso measurethat.net/Benchmarks/Show/5705/0/… Obtuve 50k / s vs 130k / s. Como puede ver, es aún más lento manipular DOM, probablemente debido a que NodeList es estático (como lo mencionaron otros). Todavía es despreciable en la mayoría de los casos de uso, pero casi 3 veces más lento, no obstante.
Szabolcs Páll
14

Editar: aunque el tipo de retorno ha cambiado en las nuevas versiones de HTML (consulte la respuesta actualizada de Tim Down), el código siguiente sigue funcionando.

Como otros han dicho, es una NodeList. Aquí hay un ejemplo completo y funcional que puedes probar:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

Esto funciona en IE 9, FF 5, Safari 5 y Chrome 12 en Win 7.

james.garriss
fuente
9

El resultado de getElementsByClassName()no es una matriz, sino un objeto tipo matriz . Específicamente se llama un HTMLCollection, no debe confundirse con NodeList( que tiene su propio forEach()método ).

Una forma simple con ES2015 para convertir un objeto tipo matriz para usar con Array.prototype.forEach()eso que aún no se ha mencionado es usar el operador de propagación o la sintaxis de propagación :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
fuente
2
Siento que esta es realmente la forma correcta de hacerlo en los navegadores modernos. Esta es la sintaxis de propagación de caso de uso exacta que se creó para resolver.
Matt Korostoff el
3

Como ya se dijo, getElementsByClassNamedevuelve una colección HTMLC , que se define como

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

Anteriormente, algunos navegadores devolvían una NodeList en su lugar.

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

La diferencia es importante, porque DOM4 ahora define NodeList s como iterable.

Según el borrador de IDL web ,

Los objetos que implementan una interfaz que se declara como soporte iterable se repiten para obtener una secuencia de valores.

Nota : En el enlace del lenguaje ECMAScript, una interfaz que es iterable tendrá "entradas", "para cada", "claves", "valores" y propiedades de iterador @@ en su objeto prototipo de interfaz .

Eso significa que, si desea usar forEach, puede usar un método DOM que devuelva una NodeList , como querySelectorAll.

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

Tenga en cuenta que esto aún no es ampliamente compatible. Ver también ¿ Cada método de Node.childNodes?

Oriol
fuente
1
Chrome 49 returnforEach in not a function
Vitaly Zdanevich
@VitalyZdanevich Prueba Chromium 50
Oriol el
En Chrome 50 estoy obteniendodocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich
@VitalyZdanevich Funcionó en Chromium 50, y aún funciona en Chromium 53. Tal vez no se consideró lo suficientemente estable como para enviarlo a Chrome 50.
Oriol
1

Esta es la forma más segura:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);
gildniy
fuente
0

getElementsByClassNamedevuelve la colección HTMLC en los navegadores modernos.

que es un objeto similar a una matriz similar a los argumentos que es iterable por for...ofbucle, vea a continuación lo que dice MDN doc al respecto:

La instrucción for ... of crea un ciclo que itera sobre objetos iterables , incluidos: String, Array, objetos tipo Array integrados (por ejemplo, argumentos o NodeList), TypedArray, Map, Set e iterables definidos por el usuario. Invoca un enlace de iteración personalizado con sentencias que se ejecutarán para el valor de cada propiedad distinta del objeto.

ejemplo

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Haritsinh Gohil
fuente
No es así, de acuerdo con el mecanografiado:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
tortugas son lindas el
@TurtlesAreCute, aquí OP está usando javascript, no mecanografiado y he respondido de acuerdo con la recomendación de vanilla js, por lo que en mecanografiado puede ser una solución diferente para el problema.
Haritsinh Gohil
@TurtlesAreCute, por cierto, también funciona en mecanografiado, pero debe mencionar el tipo correcto de variable que contiene el elemento de una clase css particular, por lo que puede emitirlo en consecuencia, para más detalles, vea esta respuesta .
Haritsinh Gohil
0

Aquí hay una prueba que creé en jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

La versión más perfumada en Chrome y Firefox es el viejo bucle for en combinación con document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

En Safari, esta variante es la ganadora:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

Si desea la variante más perfumante para todos los navegadores, podría ser esta:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
StefanSL
fuente