Il modo più efficiente per convertire un HTMLCollection in un array


392

Esiste un modo più efficiente per convertire un HTMLCollection in un array, oltre a scorrere i contenuti di detta raccolta e spingere manualmente ciascun elemento in un array?


10
Cosa si intende per "efficiente"? Se le prestazioni sono migliori, un ciclo for è generalmente più veloce di Array.prototype.slice . Un ciclo funziona anche in una più ampia varietà di browser (cioè tutti), quindi per questi criteri è il "modo più efficiente". Ed è un codice molto piccolo: for (var a=[], i=collection.length; i;) a[--i] = collection[i];quindi non c'è un "truffatore" lì :-)
RobG,

@RobG Grazie - ti darei + 59k se potessi! ;-)
Slashback

1
Osservando le attuali prestazioni del browser , lo slice ha principalmente raggiunto i loop in termini di prestazioni, tranne in Chrome. Utilizzando un numero maggiore di elementi e una leggera ottimizzazione del loop, i risultati sono quasi identici , tranne in Chrome, dove un loop è molto più veloce.
RobG

Ho creato un test jsperf che esamina sia i metodi menzionati da @harpo sia un test jquery per le prestazioni. Ho scoperto che jquery è leggermente più lento di entrambi i metodi javascript e le massime prestazioni variano tra i casi di test js. Chrome 59.0.3071 / Mac OS X 10.12.5 preferisce l'utilizzo Array.prototype.slice.calle Brave (basato su Chrome 59.0.3071) non ha praticamente alcuna differenza tra i due test javascript su più esecuzioni. Vedi jsperf.com/htmlcollection-array-vs-jquery-children
NuclearPeon

jsben.ch/h2IFA => test delle prestazioni per i modi più comuni per farlo
EscapeNetscape

Risposte:


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

avrà lo stesso effetto usando il codice "nativo".

modificare

Dato che questo ottiene molte visualizzazioni, nota (per il commento di @ oriol) che la seguente espressione più concisa è effettivamente equivalente:

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

Ma nota per il commento di @ JussiR, che a differenza della forma "dettagliata", crea un'istanza di matrice vuota, inutilizzata e davvero inutilizzabile nel processo. Ciò che i compilatori fanno al riguardo è al di fuori del ken del programmatore.

modificare

Da ECMAScript 2015 (ES 6) esiste anche Array.from :

var arr = Array.from(htmlCollection);

modificare

ECMAScript 2015 fornisce anche l' operatore spread , che è funzionalmente equivalente a Array.from(sebbene si noti che Array.fromsupporta una funzione di mappatura come secondo argomento).

var arr = [...htmlCollection];

Ho confermato che entrambi i suddetti lavori su NodeList.

Un confronto delle prestazioni per i metodi citati: http://jsben.ch/h2IFA


7
Questo errore in IE6.
Heath Borders,

29
La scorciatoia [].slice.call(htmlCollection)funziona anche.
Oriol,

1
@ChrisNielsen Sì, sono stato male informato su questo. Mi dispiace per averlo diffuso. Non mi ero reso conto di averlo affermato anche qui. Eliminato il commento per evitare confusione, ma per contesto avevo letto (o letto male) da qualche parte che tagliare un HTMLCollection lo faceva comportare come un array e una raccolta. Totalmente errato.
Erik Reppen,

3
Il collegamento [] .slice non è equivalente poiché crea anche un'istanza di array vuota inutilizzata. Tuttavia, non sono sicuro che i compilatori siano in grado di ottimizzarlo.
JussiR,

3
Array.from, cioè fromnon è supportato da IE11.
Frank Conijn,

86

non sono sicuro se questo sia il più efficiente, ma una sintassi ES6 concisa potrebbe essere:

let arry = [...htmlCollection] 

Modifica: un altro, dal commento di Chris_F:

let arry = Array.from(htmlCollection)

9
Inoltre, ES6 aggiungeArray.from()
Chris_F,

4
Fai attenzione al primo, c'è un bug sottile durante la traspilazione con babel in cui [... htmlCollection] restituirà un array con htmlCollection in quanto è solo un elemento.
Marcel M.

3
L'operatore di diffusione dell'array non funziona su htmlCollection. È applicabile solo a NodeList.
Bobby,

1
Array.from, cioè fromnon è supportato da IE11.
Frank Conijn,

Benchmark Sembra che l'operatore di spread sia più veloce di questi 2.
RedSparr0w,

20

Ho visto un metodo più conciso per ottenere Array.prototypemetodi in generale che funziona altrettanto bene. La conversione di un HTMLCollectionoggetto in un Arrayoggetto è illustrata di seguito:

[] .slice.call (yourHTMLCollectionObject);

E, come menzionato nei commenti, per i vecchi browser come IE7 e precedenti, devi semplicemente usare una funzione di compatibilità, come:

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

    return a
}

So che questa è una vecchia domanda, ma ho sentito che la risposta accettata era un po 'incompleta; quindi ho pensato di buttarlo qui FWIW.


6

Per un'implementazione cross-browser ti suggerisco di guardare la funzione prototype.js $A

copiato da 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;
}

Probabilmente non lo usa Array.prototype.sliceperché non è disponibile su tutti i browser. Temo che le prestazioni siano piuttosto cattive in quanto un fallback è un ciclo javascript su iterable.


2
L'OP ha richiesto un altro modo rispetto a "scorrere i contenuti di detta raccolta e spingere manualmente ciascun elemento in un array", ma è proprio quello che fa la $Afunzione la maggior parte delle volte.
Luc125,

1
Penso che il punto che stavo cercando di chiarire sia che non esiste un modo carino per farlo, il codice prototype.js mostra che puoi cercare un metodo "toArray" ma non riuscendo a ripetere il percorso più sicuro
Gareth Davis,

1
Ciò creerà nuovi membri non definiti in array sparsi. Dovrebbe esserci un test hasOwnProperty prima dell'assegnazione.
RobG

3

Questa è la mia soluzione personale, basata sulle informazioni qui (questo thread):

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

Dove $ A è stato descritto da Gareth Davis nel suo post:

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;
}

Se il browser supporta il modo migliore, ok, altrimenti utilizzerà il browser incrociato.


In generale, non mi aspetto che try / catch sia un modo efficiente per gestire il flusso di controllo. Puoi verificare se la funzione esiste per prima, quindi eseguire l'una o l'altra in modo un po 'più economico.
Patrick,

2
Come con la risposta di Gareth Davis, questo crea nuovi membri indefiniti in array sparsi, così [,,]diventa [undefined, undefined].
RobG

Non ho ancora avuto questo tipo di problemi. Cucisce una raccolta di 3 elementi in una matrice con 2 elementi. Per quanto riguarda il vuoto diventare indefinito, è un po 'di limitazioni JavaScript, immagino che ti aspettavi null invece che indefinito, giusto?
Gustavo

3

Funziona in tutti i browser, comprese le versioni precedenti di IE.

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

Poiché jsperf è ancora inattivo al momento, ecco un jsfiddle che confronta le prestazioni di diversi metodi. https://jsfiddle.net/qw9qf48j/


provarevar args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
Shahar Shokrani il

3

Per convertire array-like in array in modo efficiente, possiamo usare jQuery makeArray :

makeArray: converte un oggetto simile ad un array in un vero array JavaScript.

Uso:

var domArray = jQuery.makeArray(htmlCollection);

Un piccolo extra:

Se non si desidera mantenere il riferimento all'oggetto array (la maggior parte delle volte le HTMLCollections vengono modificate in modo dinamico, quindi è meglio copiarle in un altro array, in questo esempio prestare molta attenzione alle prestazioni:

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.
}

Che cos'è un array?

HTMLCollection è un "array-like"oggetto, l' array come oggetti sono simili all'oggetto della matrice ma manca un sacco della sua definizione funzionale:

Gli oggetti simili a matrici sembrano matrici. Hanno vari elementi numerati e una proprietà di lunghezza. Ma è lì che si ferma la somiglianza. Gli oggetti simili a array non hanno alcuna funzione di array e i loop di for-in non funzionano nemmeno!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.