¿Cómo convertir una lista de nodos DOM en una matriz en Javascript?

96

Tengo una función de Javascript que acepta una lista de nodos HTML, pero espera una matriz de Javascript (ejecuta algunos métodos de matriz en eso) y quiero alimentarla con la salida Document.getElementsByTagNameque devuelve una lista de nodos DOM.

Inicialmente pensé en usar algo simple como:

Array.prototype.slice.call(list,0)

Y eso funciona bien en todos los navegadores, excepto, por supuesto, en Internet Explorer que devuelve el error "Se esperaba un objeto JScript", ya que aparentemente la lista de nodos DOM devuelta por los Document.getElement*métodos no es un objeto JScript suficiente para ser el objetivo de una llamada de función.

Advertencias: no me importa escribir código específico de Internet Explorer, pero no puedo usar ninguna biblioteca Javascript como JQuery porque estoy escribiendo un widget para incrustarlo en un sitio web de terceros y no puedo cargar bibliotecas externas que creará conflictos para los clientes.

Mi último esfuerzo es iterar sobre la lista de nodos DOM y crear una matriz yo mismo, pero ¿hay una manera mejor de hacerlo?

Guss
fuente
Mejor aún, cree una función para convertir desde la lista de nodos DOM, pero esa realmente sería mi solución, creo que lo hizo bien.
Kristoffer Sall-Storgaard
> for (i = 0; i & lt; x.length; i ++) ¿Por qué obtener la longitud de NodeList en cada iteración? No solo es una pérdida de tiempo, sino que, dado que NodeLists son colecciones en vivo, si algo en el cuerpo del bucle cambia su longitud, podría hacerlo sin fin o alcanzar un índice fuera de los límites. Esto último es lo peor que puede suceder si asigna la longitud a una variable, y un error es mucho mejor que un bucle sin fin.
Esta es una pregunta realmente antigua, pero jQuery se creó con el método .noConflict específicamente para que no cause conflictos con otras bibliotecas (incluso con ella misma), lo que significa que se pueden cargar múltiples versiones de jQuery en una página. Dicho esto, es mejor evitar usar / cargar una biblioteca a menos que sea absolutamente necesario.
vol7ron
@ vol7ron: avance rápido hasta 2016, y todos todavía están tensos sobre el tamaño que las bibliotecas de JavaScript agregan a la página. Por supuesto, JQuery minificado y comprimido con gzip es de 30 KB, todavía es demasiado para transformar una lista de nodos :-)
Guss

Respuestas:

64

NodeLists son objetos host , Array.prototype.sliceno se garantiza que el método en objetos host funcione, la Especificación ECMAScript establece:

Si la función de segmento se puede aplicar correctamente a un objeto host depende de la implementación.

Le recomendaría que haga una función simple para iterar sobre NodeListy agregar cada elemento existente a una matriz:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

ACTUALIZAR:

Como sugieren otras respuestas, ahora puede usar en entornos modernos la sintaxis de propagación o el Array.frommétodo:

const array = [ ...nodeList ] // or Array.from(nodeList)

Pero pensándolo bien, supongo que el caso de uso más común para convertir un NodeList en un Array es iterar sobre él, y ahora el NodeList.prototypeobjeto tiene el forEachmétodo de forma nativa , por lo que si se encuentra en un entorno moderno, puede usarlo directamente o tener un pollyfill.

Christian C. Salvadó
fuente
2
Esto es crear una matriz con el orden original de la lista invertido, lo que supongo que no es lo que quiere el OP. ¿Querías hacer en array[i] = obj[i]lugar de array.push(obj[i])?
Tim Down
@Tim, cierto, lo tenía así antes pero lo edité ayer por la noche sin darme cuenta (3AM hora local :), ¡Gracias !.
Christian C. Salvadó
9
¿En qué circunstancias sería obj.lengthcualquier otra cosa que no sea un valor entero?
Peter
1
No puedo creer que sea tan complicado. Feo. Esa es una necesidad muy común en la programación Web / JS. ¿Un nuevo método para la próxima versión del lenguaje?
Andrew Koper
1
@AlbertoPerez, ¡de nada !. Saludos hasta Madrid!
Christian C. Salvadó
126

En es6 puede usar lo siguiente:

  • Operador de propagación

     var elements = [... nodelist]
  • Utilizando Array.from

     var elements = Array.from(nodelist)

más referencias en https://developer.mozilla.org/en-US/docs/Web/API/NodeList

camiloazula
fuente
4
tan fácil con Array.from(): D
Josan Iracheta
4
en caso de que alguien esté usando este enfoque con Typescript (a ES5), solo Array.fromfunciona, ya que TS transpila esto a nodelist.slice, lo cual no es compatible.
Peter Albert
Respondí lo mismo un año antes que tú y ¿me aprobaste en las votaciones? No puedo explicar esto ..
vsync
3
@vsync, su respuesta no mencionaArray.from
ESR
@EdmundReed - ¿entonces? ¿Cómo eso lo justifica? es más largo escribir, por lo que en situaciones reales nunca se podrá usar, solo spreadse usará.
vsync
16

Usar spread (ES2015) es tan fácil como:[...document.querySelectorAll('p')]

(opcional: use Babel para transpilar el código ES6 anterior a la sintaxis ES5)


Pruébelo en la consola de su navegador y vea la magia:

for( links of [...document.links] )
  console.log(links);
vsync
fuente
Al menos al menos en Chrome, 44, obtengo esto: Uncaught TypeError: document.querySelectorAll no es una función (…)
Nick
@OmidHezaveh - Como dije, este es el código ES6. No sé si Chrome 44 es compatible con ES6 y, de ser así, con qué cobertura. Es un navegador de casi un año y, obviamente, tendría que ejecutar este código en un navegador que admita la propagación de ES6.
vsync
O transpílelo a es5 antes de la ejecución
HelloWorld
8

Usa este sencillo truco

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})
Gena Shumilkin
fuente
¿Puede explicar por qué cree que esto tiene más posibilidades de éxito que usar Array.prototype.slice(o [].slicecomo lo dice)? Como nota, me gustaría comentar que el error específico de IE que documenté en la Q ocurre en IE 8 o inferior, donde mapno está implementado de todos modos. En IE 9 ("modo estándar") o superior, ambos slicey maptienen éxito de la misma manera.
Guss
6

Si bien no es realmente una corrección adecuada, ya que no hay ninguna especificación que requiera trabajar con elementos DOM, hice una para permitirle usar slice()de esta manera: https://gist.github.com/brettz9/6093105

ACTUALIZACIÓN : cuando planteé esto con el editor de la especificación DOM4 (preguntando si podrían agregar sus propias restricciones a los objetos del host (de modo que la especificación requiera que los implementadores conviertan correctamente estos objetos cuando se usan con métodos de matriz) más allá de la especificación ECMAScript que tenía permitido para la implementación-independencia), respondió que "los objetos de host son más o menos obsoletos según ES6 / IDL". Veo por http://www.w3.org/TR/WebIDL/#es-array que las especificaciones pueden usar este IDL para definir "objetos de matriz de plataforma" pero http://www.w3.org/TR/domcore/ doesn no parece estar usando el nuevo IDL para HTMLCollection(aunque parece que podría estar haciéndolo Element.attributesporque solo indica explícitamente que está usando WebIDL para DOMString y DOMTimeStamp). Yo veo[ArrayClass](que hereda de Array.prototype) se usa para NodeList(y NamedNodeMapahora está en desuso en favor del único elemento que todavía lo usaría Element.attributes). En cualquier caso, parece que se convertirá en estándar. El ES6 Array.fromtambién podría ser más conveniente para tales conversiones que tener que especificar Array.prototype.slicey más claro semánticamente que [].slice()(y la forma más corta, Array.slice()(una "matriz genérica"), hasta donde yo sé, no se ha convertido en un comportamiento estándar).

Brett Zamir
fuente
Actualicé para indicar que las especificaciones pueden estar moviéndose en la dirección de requerir este comportamiento.
Brett Zamir
5

Hoy, en 2018, podríamos usar ECMAScript 2015 (sexta edición) o ES6, pero no todos los navegadores pueden entenderlo (por ejemplo, IE no lo entiende todo). Si lo desea, puede usar ES6 de la siguiente manera: var array = [... NodeList];( como operador de propagación ) o var array = Array.from(NodeList);.

En otro caso (si no puede usar ES6) puede usar la forma más corta de convertir un NodeListen un Array:

var array = [].slice.call(NodeList, 0);.

Por ejemplo:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Pero si solo desea iterar sobre la DOMlista de nodos de manera fácil, entonces no necesita convertir NodeLista en Array. Es posible recorrer los elementos en un NodeListuso:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

No se sienta tentado a utilizar for...ino for each...inenumerar los elementos de la lista, ya que eso también enumerará la longitud y las propiedades del elemento NodeListy provocará errores si su secuencia de comandos asume que solo tiene que tratar con objetos de elementos. Además, for..inno se garantiza la visita a las propiedades en ningún orden en particular. for...oflos bucles recorrerán correctamente los objetos NodeList.

Ver también:

Bharata
fuente
3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

Esto debería funcionar, cruzar el navegador y obtener todos los nodos de "elementos".

Strelok
fuente
1
Esto es básicamente lo mismo que la respuesta de @ CMS, excepto que asume que solo quiero nodos de elementos, lo cual no es así.
Guss