La forma más eficiente de convertir una colección HTMLC en una matriz

392

¿Existe una forma más eficiente de convertir una colección HTMLC en una matriz, que no sea iterar a través del contenido de dicha colección y empujar manualmente cada elemento en una matriz?

Tom
fuente
10
¿Qué se entiende por "eficiente"? Si tiene el mejor rendimiento, un bucle for es generalmente más rápido que Array.prototype.slice . Un bucle también funciona en una variedad más amplia de navegadores (es decir, todos), por lo que, según esos criterios, es la "forma más eficiente". Y es muy poco código: for (var a=[], i=collection.length; i;) a[--i] = collection[i];así que no hay mucha "estafa" allí :-)
RobG
@RobG Gracias, ¡te daría + 59k si pudiera! ;-)
Slashback
1
En cuanto al rendimiento actual del navegador , el segmento se ha enganchado principalmente a los bucles en términos de rendimiento, excepto en Chrome. Usando una mayor cantidad de elementos y una ligera optimización del bucle, los resultados son casi idénticos , excepto en Chrome, donde un bucle es mucho más rápido.
RobG
Creé una prueba jsperf que analiza ambos métodos que @harpo mencionó, así como una prueba jquery de rendimiento. He descubierto que jquery es un poco más lento que los dos métodos javascript y el rendimiento máximo varía entre los casos de prueba de js. Chrome 59.0.3071 / Mac OS X 10.12.5 prefiere usar Array.prototype.slice.cally Brave (basado en Chrome 59.0.3071) prácticamente no tiene diferencia entre las dos pruebas de JavaScript en varias ejecuciones. Ver jsperf.com/htmlcollection-array-vs-jquery-children
NuclearPeon
jsben.ch/h2IFA => prueba de rendimiento para las formas más comunes de hacer esto
EscapeNetscape

Respuestas:

698
var arr = Array.prototype.slice.call( htmlCollection )

tendrá el mismo efecto usando el código "nativo".

Editar

Dado que esto obtiene muchas vistas, tenga en cuenta (según el comentario de @ oriol) que la siguiente expresión más concisa es efectivamente equivalente:

var arr = [].slice.call(htmlCollection);

Pero tenga en cuenta según el comentario de @ JussiR, que a diferencia de la forma "detallada", crea una instancia de matriz vacía, no utilizada y, de hecho, inutilizable en el proceso. Lo que los compiladores hacen al respecto está fuera del conocimiento del programador.

Editar

Desde ECMAScript 2015 (ES 6) también existe Array.from :

var arr = Array.from(htmlCollection);

Editar

ECMAScript 2015 también proporciona el operador de propagación , que es funcionalmente equivalente a Array.from(aunque tenga en cuenta que Array.fromadmite una función de mapeo como segundo argumento).

var arr = [...htmlCollection];

He confirmado que ambos de los anteriores funcionan NodeList.

Una comparación de rendimiento para los métodos mencionados: http://jsben.ch/h2IFA

harpo
fuente
77
Esto falla en IE6.
Heath Borders
29
El atajo [].slice.call(htmlCollection)también funciona.
Oriol
1
@ChrisNielsen Sí, estaba mal informado sobre eso. Perdón por difundir eso. No me di cuenta de que había dicho eso aquí también. Eliminé el comentario para evitar confusiones, pero por contexto, leí (o leí mal) en alguna parte que cortar una colección HTMLC hizo que se comportara como una matriz y una colección. Totalmente incorrecto
Erik Reppen
3
El acceso directo [] .slice no es equivalente, ya que también crea una instancia de matriz vacía no utilizada. Sin embargo, no estoy seguro de si los compiladores pueden optimizarlo.
JussiR
3
Array.from, es decir from, no es compatible con IE11.
Frank Conijn
86

No estoy seguro si este es el más eficiente, pero una sintaxis concisa de ES6 podría ser:

let arry = [...htmlCollection] 

Editar: Otro, del comentario de Chris_F:

let arry = Array.from(htmlCollection)
mido
fuente
99
Además, ES6 agregaArray.from()
Chris_F
44
Cuidado con el primero, hay un error sutil al transpirar con babel donde [... htmlCollection] devolverá una matriz con htmlCollection como su único elemento.
Marcel M.
3
El operador de distribución de matriz no funciona en htmlCollection. Solo es aplicable a NodeList.
Bobby
1
Array.from, es decir from, no es compatible con IE11.
Frank Conijn
Benchmark Parece que el operador de propagación es más rápido en estos 2.
RedSparr0w
20

Vi un método más conciso para obtener Array.prototypemétodos en general que funciona igual de bien. La conversión de un HTMLCollectionobjeto en un Arrayobjeto se muestra a continuación:

[] .slice.call (yourHTMLCollectionObject);

Y, como se menciona en los comentarios, para navegadores antiguos como IE7 y anteriores, simplemente tiene que usar una función de compatibilidad, como:

function toArray(x) {
    for(var i = 0, a = []; i < x.length; i++)
        a.push(x[i]);

    return a
}

Sé que esta es una vieja pregunta, pero sentí que la respuesta aceptada estaba un poco incompleta; así que pensé en tirar esto por ahí FWIW.

Codesmith
fuente
6

Para una implementación de navegador cruzado, te sugiero que mires prototype.js $A función

copiado de 1.6.1 :

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

Array.prototype.sliceProbablemente no se use porque no está disponible en todos los navegadores. Me temo que el rendimiento es bastante malo, ya que el retroceso es un bucle de JavaScript sobre el iterable.

Gareth Davis
fuente
2
El OP solicitó otra forma que no sea "iterar a través del contenido de dicha colección y empujar manualmente cada elemento en una matriz", pero eso es precisamente lo que hace la $Afunción la mayor parte del tiempo.
Luc125
1
Creo que el punto que estaba tratando de hacer es que no hay una buena manera de hacerlo, el código prototype.js muestra que puede buscar un método 'toArray', pero si falla esa iteración, la ruta más segura
Gareth Davis
1
Esto creará nuevos miembros indefinidos en matrices dispersas. Debería haber una prueba hasOwnProperty antes de la asignación.
RobG
3

Esta es mi solución personal, basada en la información aquí (este hilo):

var Divs = new Array();    
var Elemns = document.getElementsByClassName("divisao");
    try {
        Divs = Elemns.prototype.slice.call(Elemns);
    } catch(e) {
        Divs = $A(Elemns);
    }

Donde $ A fue descrito por Gareth Davis en su publicación:

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

Si el navegador admite la mejor manera, está bien, de lo contrario usará el navegador cruzado.

Gustavo
fuente
En general, no espero que try / catch sea una forma eficiente de administrar el flujo de control. Puede verificar si la función existe primero, luego ejecutar una u otra un poco más barato.
Patrick
2
Al igual que con la respuesta de Gareth Davis, esto crea nuevos miembros indefinidos en matrices dispersas, por lo que se [,,]convierte [undefined, undefined].
RobG
Todavía no tengo este tipo de problemas. Parece una colección de 3 elementos que da como resultado una matriz con 2 elementos. En cuanto al vacío se vuelve indefinido, es un poco de limitaciones de JavaScript, supongo que esperaba nulo en lugar de indefinido, ¿verdad?
Gustavo
3

Esto funciona en todos los navegadores, incluidas las versiones anteriores de IE.

var arr = [];
[].push.apply(arr, htmlCollection);

Dado que jsperf todavía está inactivo en este momento, aquí hay un jsfiddle que compara el rendimiento de diferentes métodos. https://jsfiddle.net/qw9qf48j/

Nicholas
fuente
probarvar args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
Shahar Shokrani
3

Para convertir una matriz en matriz de manera eficiente, podemos hacer uso de jQuery makeArray :

makeArray: convierte un objeto tipo matriz en una verdadera matriz de JavaScript.

Uso:

var domArray = jQuery.makeArray(htmlCollection);

Un poco extra:

Si no desea mantener una referencia al objeto de matriz (la mayoría de las veces, las colecciones HTMLC cambian dinámicamente, por lo que es mejor copiarlas en otra matriz, este ejemplo presta mucha atención al rendimiento:

var domDataLength = domData.length //Better performance, no need to calculate every iteration the domArray length
var resultArray = new Array(domDataLength) // Since we know the length its improves the performance to declare the result array from the beginning.

for (var i = 0 ; i < domDataLength ; i++) {
    resultArray[i] = domArray[i]; //Since we already declared the resultArray we can not make use of the more expensive push method.
}

¿Qué es una matriz?

Colección HTMLC es un "array-like"objeto, el tipo array objetos son similares a objeto de matriz, pero falta una gran cantidad de su definición funcional:

Los objetos en forma de matriz se ven como matrices. Tienen varios elementos numerados y una propiedad de longitud. Pero ahí es donde se detiene la similitud. Los objetos tipo matriz no tienen ninguna de las funciones de la matriz, ¡y los bucles for-in ni siquiera funcionan!

Shahar Shokrani
fuente