¿La forma más rápida de convertir JavaScript NodeList a Array?

251

Las preguntas respondidas anteriormente aquí dijeron que esta era la forma más rápida:

//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

Al realizar una evaluación comparativa en mi navegador, descubrí que es más de 3 veces más lento que esto:

var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

Ambos producen el mismo resultado, pero me resulta difícil creer que mi segunda versión sea la forma más rápida posible, especialmente porque la gente ha dicho lo contrario aquí.

¿Es esto una peculiaridad en mi navegador (Chromium 6)? ¿O hay una manera más rápida?

EDITAR: Para cualquier persona que se preocupe, me decidí por lo siguiente (que parece ser el más rápido en todos los navegadores que probé):

//nl is a NodeList
var l = []; // Will hold the array of Node's
for(var i = 0, ll = nl.length; i != ll; l.push(nl[i++]));

EDIT2: encontré una forma aún más rápida

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));
jairajs89
fuente
2
arr[arr.length] = nl[i];puede ser más rápido que arr.push(nl[i]);ya que evita una llamada de función.
Luc125
99
Esta página jsPerf realiza un seguimiento de todas las respuestas en esta página: jsperf.com/nodelist-to-array/27
pilau
Tenga en cuenta que "EDIT2: Encontré una forma más rápida" es un 92% más lento en IE8.
Camilo Martin
2
Como ya sabes cuántos nodos tienes:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems
@ Luc125 Depende del navegador, ya que la implementación de inserción puede estar optimizada, estoy pensando en Chrome porque v8 es bueno con este tipo de cosas.
axelduch

Respuestas:

197

El segundo tiende a ser más rápido en algunos navegadores, pero el punto principal es que debe usarlo porque el primero simplemente no es cruzado. A pesar de que The Times They are a-Changin '

@kangax ( vista previa de IE 9 )

Array.prototype.slice ahora puede convertir ciertos objetos host (por ejemplo, NodeList) en matrices, algo que la mayoría de los navegadores modernos han podido hacer durante bastante tiempo.

Ejemplo:

Array.prototype.slice.call(document.childNodes);
gblazex
fuente
??? Ambos son compatibles entre navegadores: Javascript (al menos si afirma ser compatible con la especificación ECMAscript) es Javascript; La matriz, el prototipo, el segmento y la llamada son características del lenguaje principal + tipos de objeto.
Jason S
66
pero no se pueden usar en NodeLists en IE (sé que apesta, pero oye ver mi actualización)
gblazex
99
Debido a que las NodeLists no son parte del lenguaje, son parte de la API DOM, que se sabe que es defectuosa / impredecible, especialmente en IE
gblazex
3
Array.prototype.slice no es un navegador cruzado, si tiene en cuenta IE8.
Lajos Meszaros
1
Sí, de eso se trataba básicamente mi respuesta :) Aunque fue más relevante en 2010 que en hoy (2015).
gblazex
224

Con ES6, ahora tenemos una forma sencilla de crear una matriz a partir de una NodeList: la Array.from()función.

// nl is a NodeList
let myArray = Array.from(nl)
webdif
fuente
¿Cómo se compara en velocidad este nuevo método ES6 con los otros mencionados anteriormente?
user5508297
10
@ user5508297 Mejor que el truco de llamada de corte. Más lento que los bucles for, pero eso no es exactamente lo que queremos tener una matriz sin atravesarla. ¡Y la sintaxis es hermosa, simple y fácil de recordar!
webdif
Lo bueno de Array.from es que puede usar el argumento del mapa:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde
103

Aquí hay una nueva forma genial de hacerlo utilizando el operador de propagación ES6 :

let arr = [...nl];
Alexander Olsson
fuente
77
No funcionó para mí en mecanografiado. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian
1
Viene en 3er lugar más rápido, detrás de los 3 métodos de cambio, usando Chrome 71: jsperf.com/nodelist-to-array/90
BrianFreud
19

Algunas optimizaciones:

  • guardar la longitud de la NodeList en una variable
  • establezca explícitamente la longitud de la nueva matriz antes de configurar.
  • acceder a los índices, en lugar de empujar o no cambiar.

Código ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
tailandés
fuente
Puede reducir un poco más de tiempo utilizando Array (longitud) en lugar de crear la matriz y luego dimensionarla por separado. Si luego usa const para declarar la matriz y su longitud, con un let dentro del ciclo, termina aproximadamente 1.5% más rápido que el método anterior: const a = Array (nl.length), c = a.length; para (let b = 0; b <c; b ++) {a [b] = nl [b]; } ver jsperf.com/nodelist-to-array/93
BrianFreud el
15

Los resultados dependerán completamente del navegador, para dar un veredicto objetivo, tenemos que hacer algunas pruebas de rendimiento, aquí hay algunos resultados, puede ejecutarlos aquí :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

Vista previa de la plataforma IE9 3:

CMS
fuente
1
Me pregunto cómo se mantiene el bucle inverso para estos ... for (var i=o.length; i--;)... ¿el 'bucle for' en estas pruebas reevaluó la propiedad de longitud en cada iteración?
Dagg Nabbit
14

El navegador más rápido y cruzado es

for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);

Como lo comparé en

http://jsbin.com/oqeda/98/edit

* ¡Gracias @CMS por la idea!

Cromo (similar a Google Chrome) Firefox Ópera

Felipe Buccioni
fuente
1
el enlace parece estar equivocado, debería ser 91, en lugar de 89 para incluir la prueba que mencionas. Y 98 parece el más completo.
Yaroslav Yakovlev
9

En ES6 puedes usar:

  • Array.from

    let array = Array.from(nodelist)

  • Operador extendido

    let array = [...nodelist]

Isabela
fuente
No agrega nada nuevo de lo que ya se ha escrito en respuestas anteriores.
Stefan Becker
7
NodeList.prototype.forEach = Array.prototype.forEach;

Ahora puede hacer document.querySelectorAll ('div'). ForEach (function () ...)

John Williams
fuente
Buena idea, gracias @John! Sin embargo, NodeListno funciona pero sí Object: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell
3
No use Object.prototype: rompe JQuery y un montón de cosas como la sintaxis literal del diccionario.
Nate Symer
Claro, evite extender las funciones integradas nativas.
roland
5

más rápido y más corto:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
anónimo
fuente
3
¿Por qué el >>>0? ¿Y por qué no poner las tareas en la primera parte del ciclo for?
Camilo Martin
55
Además, esto tiene errores. Cuando les así 0, el ciclo finalizará, por lo tanto, el 0elemento th no se copiará (recuerde que hay un elemento en el índice 0)
Camilo Martin
1
Me encanta esta respuesta, pero ... Cualquiera que se pregunte: >>> puede que no sea necesario aquí, pero se utiliza para garantizar que la longitud de la lista de nodos se adhiera a las especificaciones de la matriz; asegura que es un entero de 32 bits sin signo. Compruébelo aquí ecma-international.org/ecma-262/5.1/#sec-15.4 Si le gusta el código ilegible, ¡use este método con las sugerencias de @ CamiloMartin!
Todd
En respuesta a @CamiloMartin: es arriesgado poner vardentro de la primera parte de un forbucle debido a la 'elevación variable'. La declaración de var'se izará' en la parte superior de la función, incluso si la varlínea aparece en algún lugar más abajo, y esto puede causar efectos secundarios que no son obvios en la secuencia del código. Por ejemplo algo de código en la misma función que ocurre antes el bucle podría depender en ay lsiendo no declarado. Por lo tanto, para una mayor capacidad de predicción, declare sus variables en la parte superior de la función (o si está en ES6, use consto en su letlugar, que no se iza).
brennanyoung
3

Mira esta publicación de blog aquí que habla sobre lo mismo. Por lo que deduzco, el tiempo extra podría tener que ver con subir la cadena del telescopio.

Vivin Paliath
fuente
Interesante. Acabo de hacer algunas pruebas similares ahora y Firefox 3.6.3 no muestra un aumento en la velocidad de ninguna manera, mientras que Opera 10.6 tiene un aumento del 20% y Chrome 6 tiene un aumento del 230% (!) Haciéndolo de forma manual.
jairajs89
@ jairajs89 bastante extraño. Parece que Array.prototype.slicedepende del navegador. Me pregunto qué algoritmo está usando cada uno de los navegadores.
Vivin Paliath
3

Esta es la función que uso en mi JS:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}
Diseñador web
fuente
1

Aquí hay gráficos actualizados a la fecha de esta publicación (el cuadro de "plataforma desconocida" es Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

A partir de estos resultados, parece que el método de preasignación 1 es la apuesta más segura entre navegadores.

TomSlick
fuente
1

Suponiendo nodeList = document.querySelectorAll("div"), esta es una forma concisa de convertir nodelista matriz.

var nodeArray = [].slice.call(nodeList);

Véame usarlo aquí .

Udo E.
fuente