Der schnellste Weg, um JavaScript NodeList in Array zu konvertieren?

251

Zuvor beantwortete Fragen hier sagten, dass dies der schnellste Weg war:

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

Beim Benchmarking in meinem Browser habe ich festgestellt, dass es mehr als dreimal langsamer ist als dieses:

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

Beide produzieren die gleiche Ausgabe, aber ich kann kaum glauben, dass meine zweite Version der schnellstmögliche Weg ist, zumal die Leute hier etwas anderes gesagt haben.

Ist das eine Eigenart in meinem Browser (Chromium 6)? Oder gibt es einen schnelleren Weg?

EDIT: Für alle, die sich interessieren, habe ich mich für Folgendes entschieden (das in jedem von mir getesteten Browser am schnellsten zu sein scheint):

//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: Ich habe einen noch schnelleren Weg gefunden

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));
jairajs89
quelle
2
arr[arr.length] = nl[i];kann schneller sein als arr.push(nl[i]);da es einen Funktionsaufruf vermeidet.
Luc125
9
Diese jsPerf-Seite verfolgt alle Antworten auf dieser Seite: jsperf.com/nodelist-to-array/27
pilau
Bitte beachten Sie, dass "EDIT2: Ich habe einen schnelleren Weg gefunden" auf IE8 92% langsamer ist.
Camilo Martin
2
Da Sie bereits wissen, wie viele Knoten Sie haben:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
Mems
@ Luc125 Es hängt vom Browser ab, da die Push-Implementierung möglicherweise optimiert ist. Ich denke über Chrome nach, weil v8 mit solchen Dingen gut ist.
Axelduch

Antworten:

197

Der zweite ist in einigen Browsern tendenziell schneller, aber der Hauptpunkt ist, dass Sie ihn verwenden müssen, da der erste einfach nicht browserübergreifend ist. Auch wenn die Zeiten, in denen sie a-Changin sind

@ Kangax ( IE 9 Vorschau )

Array.prototype.slice kann jetzt bestimmte Hostobjekte (z. B. NodeLists) in Arrays konvertieren - etwas, was die meisten modernen Browser schon seit einiger Zeit können.

Beispiel:

Array.prototype.slice.call(document.childNodes);
gblazex
quelle
??? Beide sind browserübergreifend kompatibel - Javascript (zumindest wenn es behauptet, mit der ECMAscript-Spezifikation kompatibel zu sein) ist Javascript; Array, Prototyp, Slice und Aufruf sind alle Funktionen der Kernsprache + Objekttypen.
Jason S
6
aber sie können nicht auf NodeLists im IE verwendet werden (ich weiß, dass es scheiße ist, aber hey, sieh mein Update)
gblazex
9
Da NodeLists nicht Teil der Sprache sind, sind sie Teil der DOM-API, die bekanntermaßen
fehlerhaft
3
Array.prototype.slice ist nicht browserübergreifend, wenn Sie IE8 berücksichtigen.
Lajos Meszaros
1
Ja, darum ging es in meiner Antwort im Grunde :) Obwohl es 2010 relevanter war als heute (2015).
Gblazex
224

Mit ES6 haben wir jetzt eine einfache Möglichkeit, ein Array aus einer NodeList zu erstellen: die Array.from()Funktion.

// nl is a NodeList
let myArray = Array.from(nl)
webdif
quelle
Wie vergleicht sich diese neue ES6-Methode in der Geschwindigkeit mit den anderen oben genannten?
user5508297
10
@ user5508297 Besser als der Slice-Call-Trick. Langsamer als die for-Schleifen, aber das ist nicht genau das, was wir wollen, um ein Array zu haben, ohne es zu durchlaufen. Und die Syntax ist schön, einfach und leicht zu merken!
Webdif
Das Schöne an Array.from ist, dass Sie das Map-Argument verwenden können:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
Honzajde
103

Hier ist eine neue coole Möglichkeit, dies mit dem ES6-Spread-Operator zu tun :

let arr = [...nl];
Alexander Olsson
quelle
7
Hat bei mir im Typoskript nicht funktioniert. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian
1
Kommt mit Chrome 71 auf den drittschnellsten Platz hinter den drei nicht verschobenen Methoden: jsperf.com/nodelist-to-array/90
BrianFreud
19

Einige Optimierungen:

  • Speichern Sie die Länge der NodeList in einer Variablen
  • Legen Sie die Länge des neuen Arrays vor dem Festlegen explizit fest.
  • Zugriff auf die Indizes, anstatt zu verschieben oder zu verschieben.

Code ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
Thai
quelle
Sie können etwas mehr Zeit sparen, indem Sie Array (Länge) verwenden, anstatt das Array zu erstellen und es dann separat zu dimensionieren. Wenn Sie dann const verwenden, um das Array und seine Länge mit einem Let innerhalb der Schleife zu deklarieren, endet es ungefähr 1,5% schneller als die obige Methode: const a = Array (nl.length), c = a.length; für (sei b = 0; b <c; b ++) {a [b] = nl [b]; } siehe jsperf.com/nodelist-to-array/93
BrianFreud
15

Die Ergebnisse hängen vollständig vom Browser ab. Um ein objektives Urteil zu fällen, müssen wir einige Leistungstests durchführen. Hier sind einige Ergebnisse, die Sie ausführen können hier :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

IE9 Platform Preview 3:

CMS
quelle
1
Ich frage mich, wie sich die Umkehrung der for-Schleife gegen diese hält. for (var i=o.length; i--;)Hat die 'for-Schleife' in diesen Tests die Längeneigenschaft bei jeder Iteration neu bewertet?
Dagg Nabbit
14

Der schnellste und browserübergreifendste ist

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

Wie ich in verglichen habe

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

* Danke @CMS für die Idee!

Chrom (ähnlich wie Google Chrome) Feuerfuchs Oper

Felipe Buccioni
quelle
1
Der Link scheint falsch zu sein, sollte 91 statt 89 sein, um den von Ihnen erwähnten Test einzuschließen. Und 98 scheint die vollständigste zu sein.
Jaroslaw Jakowlew
9

In ES6 können Sie entweder Folgendes verwenden:

  • Array.from

    let array = Array.from(nodelist)

  • Spread-Operator

    let array = [...nodelist]

Isabella
quelle
Fügt nichts Neues hinzu, was bereits in früheren Antworten geschrieben wurde.
Stefan Becker
7
NodeList.prototype.forEach = Array.prototype.forEach;

Jetzt können Sie document.querySelectorAll ('div'). ForEach (function () ...) ausführen.

John Williams
quelle
Gute Idee, danke @John! Funktioniert jedoch NodeListnicht, sondern Object: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell
3
Verwenden Sie Object.prototype nicht: Es bricht JQuery und eine Menge Dinge wie die Literal-Syntax des Wörterbuchs.
Nate Symer
Vermeiden Sie es natürlich, native integrierte Funktionen zu erweitern.
Roland
5

schneller und kürzer:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
anonym
quelle
3
Warum das >>>0? Und warum nicht die Zuweisungen in den ersten Teil der for-Schleife setzen?
Camilo Martin
5
Auch das ist fehlerhaft. Wenn dies der Fall list 0, endet die Schleife, daher wird das 0dritte Element nicht kopiert (denken Sie daran, dass sich ein Element im Index befindet 0)
Camilo Martin,
1
Ich liebe diese Antwort, aber ... Jeder, der sich wundert: Das ist hier >>> möglicherweise nicht notwendig, wird aber verwendet, um zu gewährleisten, dass die Länge der Knotenliste der Array-Spezifikation entspricht. Es stellt sicher, dass es sich um eine vorzeichenlose 32-Bit-Ganzzahl handelt. Überprüfen Sie es hier ecma-international.org/ecma-262/5.1/#sec-15.4 Wenn Sie unlesbaren Code mögen, verwenden Sie diese Methode mit den Vorschlägen von @ CamiloMartin!
Todd
Als Antwort auf @CamiloMartin - Es ist riskant, varden ersten Teil einer forSchleife zu platzieren, da es sich um ein "variables Heben" handelt. Die Deklaration von varwird an den oberen varRand der Funktion "gehisst", auch wenn die Zeile irgendwo weiter unten angezeigt wird. Dies kann zu Nebenwirkungen führen, die aus der Codesequenz nicht ersichtlich sind. Zum Beispiel auftreten einige Code in der gleichen Funktion vor der for - Schleife könnte abhängen auf aund lnicht deklariert werden. Deklarieren Sie daher für eine bessere Vorhersagbarkeit Ihre Vars oben in der Funktion (oder verwenden Sie bei ES6 constoder letstattdessen, die nicht hochgezogen werden).
brennanyoung
3

Schauen Sie sich diesen Blog-Beitrag hier an , der über dasselbe spricht. Soweit ich weiß, hat die zusätzliche Zeit möglicherweise damit zu tun, die Zielfernrohrkette hinaufzugehen.

Vivin Paliath
quelle
Interessant. Ich habe gerade einige ähnliche Tests durchgeführt und Firefox 3.6.3 zeigt keine Geschwindigkeitssteigerung in beiden Richtungen, während Opera 10.6 eine Steigerung von 20% und Chrome 6 eine Steigerung von 230% (!) Bei manueller Iteration-Push-Methode aufweist.
Jairajs89
@ Jairajs89 ziemlich seltsam. Es scheint, dass das Array.prototype.slicebrowserabhängig ist. Ich frage mich, welchen Algorithmus jeder Browser verwendet.
Vivin Paliath
3

Dies ist die Funktion, die ich in meinem JS verwende:

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

Hier sind die zum Datum dieser Veröffentlichung aktualisierten Diagramme (Diagramm "unbekannte Plattform" ist Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

Aus diesen Ergebnissen geht hervor, dass die Methode der Vorbelegung 1 die sicherste Cross-Browser-Wette ist.

TomSlick
quelle
1

Angenommen nodeList = document.querySelectorAll("div"), dies ist eine prägnante Form der Konvertierung nodelistin ein Array.

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

Sehen Sie, wie ich es hier benutze .

Udo E.
quelle