¿Por qué la lista de nodos no tiene forEach?

91

Estaba trabajando en un breve script para cambiar <abbr>el texto interno de los elementos, pero descubrí que nodelistno tiene un forEachmétodo. Sé que nodelistno se hereda de Array, pero ¿no parece forEachque sería un método útil tener? ¿Hay un problema de implementación en particular no soy consciente de que impide que se agregue forEacha nodelist?

Nota: Soy consciente de que Dojo y jQuery tienen forEachde alguna forma sus nodelists. No puedo usar ninguno de los dos por limitaciones.

Serpientes y Café
fuente
6
¡Hola desde el futuro! nodeList tiene forEach desde ES6 .
Blaise

Respuestas:

94

NodeList ahora tiene forEach () en todos los navegadores principales

Consulte nodeList forEach () en MDN .

Respuesta original

Ninguna de estas respuestas explica por qué NodeList no hereda de Array, lo que le permite tener forEachy todo lo demás.

La respuesta se encuentra en este hilo es-discusion . En resumen, rompe la web:

El problema era el código que asumía incorrectamente que instanceof significaba que la instancia era un Array en combinación con Array.prototype.concat.

Hubo un error en la biblioteca de cierre de Google que causó que casi todas las aplicaciones de Google fallaran debido a esto. La biblioteca se actualizó tan pronto como se encontró esto, pero es posible que todavía haya código que haga la misma suposición incorrecta en combinación con concat.

Es decir, algún código hizo algo como

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

Sin embargo, concattratará las matrices "reales" (no la instancia de Array) de manera diferente a otros objetos:

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

lo que significa que el código anterior se rompió cuando xera una NodeList, porque antes iba por el doSomethingElseWith(x)camino, mientras que después seguía por el otherArray.concat(x)camino, lo que hizo algo extraño ya que xno era una matriz real.

Durante algún tiempo hubo una propuesta para una Elementsclase que era una subclase real de Array, y se usaría como "la nueva NodeList". Sin embargo, eso se eliminó del estándar DOM , al menos por ahora, ya que aún no era factible de implementar por una variedad de razones técnicas y relacionadas con las especificaciones.

Domenic
fuente
11
Me parece una mala llamada. En serio, creo que es la decisión correcta romper cosas de vez en cuando, especialmente si eso significa que tenemos API sanas para el futuro. Además, no es que la web esté ni siquiera cerca de ser una plataforma estable, la gente está acostumbrada a que su javascript de 2 años ya no funcione como se esperaba ..
JoyalToTheWorld
3
"Rompe la Web"! = "Rompe cosas de Google"
Matt
¿Por qué no tomar prestado el método forEach de Array.prototype? Por ejemplo, en lugar de agregar Array como prototipo, simplemente haga esto. ForEach = Array.prototype.forEach en el constructor, o incluso simplemente implemente forEach únicamente para NodeList?
PopKernel
1
Nota; esta actualización NodeList now has forEach() in all major browsersparece implicar que IE no es un navegador importante. Con suerte, eso es cierto para algunas personas, pero no es cierto para mí (todavía).
Graham P Heath
58

Tu puedes hacer

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

    // Your code here.

} );
akuhn
fuente
3
Array.prototype.forEach.callse puede acortar a[].forEach.call
CodeBrauer
7
@CodeBrauer: eso no es solo acortar Array.prototype.forEach.call, es crear una matriz vacía y usar su forEachmétodo.
Paul D. Waite
34

Puede considerar crear una nueva matriz de nodos.

  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 es solo una lista / matriz de referencias de nodos que estamos creando aquí, no hay nodos duplicados.

  nodes[0] === nodeList[0] // will be true
sbr
fuente
22
O simplemente hazlo Array.prototype.forEach.call(nodeList, fun).
akuhn
También sugeriría un alias a la función forEach tal que: var forEach = Array.prototype.forEach.call(nodeList, callback);. Ahora puede simplemente llamarforEach(nodeList, callback);
Andrew Craswell
19

Nunca digas nunca, es 2016 y el NodeListobjeto ha implementado un forEachmétodo en la última versión de Chrome (v52.0.2743.116).

Es demasiado pronto para usarlo en producción, ya que otros navegadores aún no lo admiten (FF 49 probado), pero supongo que pronto se estandarizará.

mayoman
fuente
2
Opera también lo admite y se agregará soporte en la versión 50 de Firefox, cuyo lanzamiento está programado para el 15/11/16.
Shaggy
1
Aunque está implementado, no forma parte de ningún estándar. Aún así, es mejor hacer lo Array.prototype.slice.call(nodelist).forEach(…)que es estándar y funciona en navegadores antiguos.
Nate
17

En resumen, es un conflicto de diseño implementar ese método.

De MDN:

¿Por qué no puedo usar forEach o map en una NodeList?

NodeList se utilizan de forma muy similar a las matrices y sería tentador utilizar métodos Array.prototype en ellas. Sin embargo, esto es imposible.

JavaScript tiene un mecanismo de herencia basado en prototipos. Las instancias de matriz heredan métodos de matriz (como forEach o map) porque su cadena de prototipos se parece a la siguiente:

myArray --> Array.prototype --> Object.prototype --> null (la cadena prototipo de un objeto se puede obtener llamando varias veces a Object.getPrototypeOf)

forEach, map y similares son propiedades propias del objeto Array.prototype.

A diferencia de las matrices, la cadena de prototipos NodeList tiene el siguiente aspecto:

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

NodeList.prototype contiene el método item, pero ninguno de los métodos Array.prototype, por lo que no se pueden utilizar en NodeLists.

Fuente: https://developer.mozilla.org/en-US/docs/DOM/NodeList (desplácese hacia abajo hasta ¿Por qué no puedo usar forEach o mapa en una NodeList? )

Matt Lo
fuente
8
Entonces, dado que es una lista, ¿por qué está diseñada de esa manera? ¿Qué les impedía hacer cadena myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null?
Igor Pantović
14

Si desea usar forEach en NodeList, simplemente copie esa función de Array:

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

Eso es todo, ahora puede usarlo de la misma manera que lo haría para Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
AlexTR
fuente
4
Excepto que mutar las clases base no es muy explícito para el lector promedio. En otras palabras, en el fondo del código, debe recordar cada personalización que realice en los objetos base del navegador. Confiar en la documentación de MDN ya no es útil porque los objetos han cambiado el comportamiento de la norma. Es mejor aplicar explícitamente el prototipo en el momento de la llamada para que el lector pueda darse cuenta fácilmente de que forEach es una idea prestada y no algo que sea parte de la definición del lenguaje. Vea la respuesta que @akuhn tiene arriba.
Sukima
@Sukima equivocado por premisas equivocadas. En este caso específico, el enfoque dado corrige NodeList para que se comporte como espera el desarrollador. Esta es la forma más adecuada de solucionar el problema de clase del sistema. (NodeList parece estar inacabado y debería corregirse en futuras versiones del lenguaje).
Daniel Garmoshka
1
ahora que NodeList.forEach existe, ¡esto se convierte en un polyfill hilarantemente simple!
Damon
7

En ES2015, ahora puede usar el forEachmétodo para la lista de nodos.

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

Ver el enlace MDN

Sin embargo, si desea usar colecciones HTML u otros objetos tipo matriz, en es2015, puede usar el Array.from()método. Este método toma un objeto iterable o similar a una matriz (incluidas nodeList, colecciones HTML, cadenas, etc.) y devuelve una nueva instancia de matriz. Puedes usarlo así:

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

Como el Array.from()método es intercambiable, puede usarlo en un código es5 como este

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

Para obtener más información, consulte la página MDN .

Para comprobar la compatibilidad del navegador actual .

O

otra forma de es2015 es utilizar el operador de propagación.

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

Operador de propagación MDN

Operador de propagación: compatibilidad con el navegador

Mishel Tanvir Habib
fuente
2

Mi solución:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Bakos Bence
fuente
1
A menudo, no es una buena idea extender la funcionalidad de DOM a través de prototipos, especialmente en versiones anteriores de IE ( artículo ).
KFE
0

NodeList es parte de DOM API. Mire los enlaces de ECMAScript que también se aplican a JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . NodeList y una propiedad de longitud de solo lectura y una función de elemento (índice) para devolver un nodo.

La respuesta es, tienes que iterar. No hay alternativa. Foreach no funcionará. Trabajo con enlaces de API DOM de Java y tengo el mismo problema.

randominstanceOfLivingThing
fuente
Pero, ¿hay alguna razón en particular por la que no debería implementarse? Tanto jQuery como Dojo lo han implementado en sus propias bibliotecas
Snakes and Coffee
2
pero ¿cómo es un conflicto de diseño?
Serpientes y café
0

Compruebe MDN para NodeList.forEach especificación.

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

En IE, use la respuesta de akuhn :

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