Il modo più veloce per convertire JavaScript NodeList in array?


251

Le domande a cui era stata data risposta in precedenza qui dicevano che questo era il modo più veloce:

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

Nel benchmarking sul mio browser ho scoperto che è più di 3 volte più lento di questo:

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

Entrambi producono lo stesso output, ma trovo difficile credere che la mia seconda versione sia il modo più veloce possibile, soprattutto perché qui la gente ha detto diversamente.

È una stranezza nel mio browser (Chromium 6)? O c'è un modo più veloce?

EDIT: Per chiunque si preoccupi, ho optato per quanto segue (che sembra essere il più veloce in ogni browser che ho testato):

//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: ho trovato un modo ancora più veloce

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));

2
arr[arr.length] = nl[i];potrebbe essere più veloce rispetto a arr.push(nl[i]);quando evita una chiamata di funzione.
Luc125,

9
Questa pagina jsPerf tiene traccia di tutte le risposte in questa pagina: jsperf.com/nodelist-to-array/27
pilau

Si noti che "EDIT2: ho trovato un modo più veloce" è più lento del 92% su IE8.
Camilo Martin,

2
Dato che sai già sai quanti nodi hai:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems

@ Luc125 Dipende dal browser, poiché l'implementazione push può essere ottimizzata, sto pensando a Chrome perché v8 è buono con questo tipo di cose.
axelduch,

Risposte:


197

Il secondo tende ad essere più veloce in alcuni browser, ma il punto principale è che devi usarlo perché il primo non è semplicemente cross-browser. Anche se i tempi sono un cambiamento

@kangax ( anteprima di IE 9 )

Array.prototype.slice ora può convertire alcuni oggetti host (ad esempio NodeList) in array, cosa che la maggior parte dei browser moderni è stata in grado di fare per un bel po '.

Esempio:

Array.prototype.slice.call(document.childNodes);

??? Entrambi sono compatibili con più browser - Javascript (almeno se afferma di essere compatibile con le specifiche ECMAscript) è Javascript; Array, prototype, slice e call sono tutte caratteristiche del linguaggio principale + dei tipi di oggetto.
Jason S

6
ma non possono essere usati su NodeLists in IE (so che fa schifo, ma hey vedi il mio aggiornamento)
gblazex,

9
poiché le NodeList non fanno parte del linguaggio, fanno parte dell'API DOM, che è nota per essere buggy / imprevedibile soprattutto in IE
gblazex,

3
Array.prototype.slice non è cross browser, se si considera IE8 in considerazione.
Lajos Meszaros,

1
Sì, questa è la mia risposta fondamentalmente :) Anche se era più rilevante nel 2010 rispetto ad oggi (2015).
gblazex,

224

Con ES6, ora abbiamo un modo semplice per creare un array da un NodeList: la Array.from()funzione.

// nl is a NodeList
let myArray = Array.from(nl)

come si confronta questo nuovo metodo ES6 in velocità con gli altri che sono stati menzionati sopra?
user5508297

10
@ user5508297 Meglio del trucco della chiamata slice. Più lento dei loop for, ma non è esattamente ciò che potremmo desiderare di avere un array senza attraversarlo. E la sintassi è bella, semplice e facile da ricordare!
webdif,

La cosa bella di Array.from è che puoi usare l'argomento map:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde


19

Alcune ottimizzazioni:

  • salva la lunghezza della NodeList in una variabile
  • impostare esplicitamente la lunghezza del nuovo array prima di impostare.
  • accedere agli indici, piuttosto che spingere o spostare.

Codice ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}

Puoi radere un po ' più di tempo usando Array (lunghezza) anziché creare l'array e quindi dimensionarlo separatamente. Se poi usi const per dichiarare l'array e la sua lunghezza, con un let all'interno del loop, finisce circa l'1,5% più veloce del metodo sopra: const a = Array (nl.length), c = a.length; per (let b = 0; b <c; b ++) {a [b] = nl [b]; } vedi jsperf.com/nodelist-to-array/93
BrianFreud

15

I risultati dipenderanno completamente dal browser, per dare un verdetto oggettivo, dobbiamo fare alcuni test delle prestazioni, ecco alcuni risultati, puoi eseguirli qui :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

Anteprima della piattaforma IE9 3:


1
Mi chiedo come il contrario di loop regge contro questi ... for (var i=o.length; i--;)... il 'for loop' in questi test ha rivalutato la proprietà length su ogni iterazione?
Dagg Nabbit l'


9

In ES6 puoi usare:

  • Array.from

    let array = Array.from(nodelist)

  • Operatore di diffusione

    let array = [...nodelist]


Non aggiunge nulla di nuovo a ciò che è già stato scritto nelle risposte precedenti.
Stefan Becker,

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

Ora puoi fare document.querySelectorAll ('div'). ForEach (function () ...)


Buona idea, grazie @ Giovanni! Tuttavia, NodeListnon funziona ma Objectè: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell il

3
Non usare Object.prototype: rompe JQuery e un sacco di cose come la sintassi letterale del dizionario.
Nate Symer,

Certo, evitare di estendere le funzioni integrate native.
roland,

5

più veloce e più breve:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
Perché il >>>0? E perché non inserire i compiti nella prima parte del ciclo for?
Camilo Martin,

5
Inoltre, questo è un bug. Quando lè 0, il ciclo finirà, quindi l' 0elemento th non verrà copiato (ricorda che c'è un elemento all'indice 0)
Camilo Martin

1
Adoro questa risposta, ma ... Chiunque si stia chiedendo: >>> potrebbe non essere necessario qui, ma viene utilizzato per garantire che la lunghezza della lista dei nomi aderisca alle specifiche dell'array; assicura che sia un numero intero a 32 bit senza segno. Dai un'occhiata qui ecma-international.org/ecma-262/5.1/#sec-15.4 Se ti piace il codice illeggibile, usa questo metodo con i suggerimenti di @ CamiloMartin!
Todd,

In risposta a @CamiloMartin - È rischioso inserire varla prima parte di un forciclo a causa del "sollevamento variabile". La dichiarazione di varverrà "sollevata" nella parte superiore della funzione, anche se la varlinea appare da qualche parte in basso, e ciò può causare effetti collaterali che non sono ovvi dalla sequenza del codice. Per esempio codice nella stessa funzione che si verificano prima del ciclo for potrebbe dipendere su ae ldi essere sommerso. Pertanto, per una maggiore prevedibilità, dichiarare i propri vars nella parte superiore della funzione (o se su ES6, utilizzare consto letinvece, che non si solleva).
brennanyoung,

3

Dai un'occhiata a questo post sul blog qui che parla la stessa cosa. Da quello che raccolgo, il tempo extra potrebbe avere a che fare con il camminare lungo la catena di portata.


Interessante. Ho appena fatto alcuni test simili ora e Firefox 3.6.3 non mostra alcun aumento di velocità in entrambi i modi, mentre Opera 10.6 ha un aumento del 20% e Chrome 6 ha un aumento del 230% (!) Facendolo nel modo manuale iterate-push.
jairajs89,

@ jairajs89 abbastanza strano. Sembra che Array.prototype.slicedipende dal browser. Mi chiedo quale algoritmo stia utilizzando ciascuno dei browser.
Vivin Paliath,

3

Questa è la funzione che uso nel mio JS:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}

1

Ecco i grafici aggiornati alla data di pubblicazione (il grafico "piattaforma sconosciuta" è Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

Da questi risultati, sembra che il metodo preallocato 1 sia la scommessa cross-browser più sicura.


1

Supponendo nodeList = document.querySelectorAll("div"), questa è una forma concisa di conversione nodelistin array.

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

Vedimi usarlo qui .

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.