Le moyen le plus rapide de convertir JavaScript NodeList en tableau?

251

Les questions précédemment répondues ici indiquaient que c'était le moyen le plus rapide:

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

En comparant sur mon navigateur, j'ai constaté qu'il est plus de 3 fois plus lent que cela:

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

Ils produisent tous deux la même sortie, mais j'ai du mal à croire que ma deuxième version est le moyen le plus rapide possible, d'autant plus que les gens ont dit le contraire ici.

Est-ce une bizarrerie dans mon navigateur (Chromium 6)? Ou existe-t-il un moyen plus rapide?

EDIT: Pour tous ceux qui s'en soucient, je me suis installé sur ce qui suit (qui semble être le plus rapide dans tous les navigateurs que j'ai testés):

//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: J'ai trouvé un moyen encore plus rapide

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));
jairajs89
la source
2
arr[arr.length] = nl[i];peut être plus rapide que arr.push(nl[i]);cela car il évite un appel de fonction.
Luc125
9
Cette page jsPerf garde une trace de toutes les réponses sur cette page: jsperf.com/nodelist-to-array/27
pilau
Veuillez noter que "EDIT2: J'ai trouvé un moyen plus rapide" est 92% plus lent sur IE8.
Camilo Martin
2
Puisque vous savez déjà combien de nœuds vous avez:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems
@ Luc125 Cela dépend du navigateur, puisque l'implémentation push peut être optimisée, je pense au chrome car la v8 est bonne avec ce genre de choses.
axelduch

Réponses:

197

Le second a tendance à être plus rapide dans certains navigateurs, mais l'essentiel est que vous devez l'utiliser car le premier n'est tout simplement pas multi-navigateur. Même si les temps sont changeants

@kangax ( aperçu IE 9 )

Array.prototype.slice peut désormais convertir certains objets hôtes (par exemple NodeList) en tableaux - ce que la majorité des navigateurs modernes ont pu faire pendant un bon moment.

Exemple:

Array.prototype.slice.call(document.childNodes);
gblazex
la source
??? Les deux sont compatibles avec tous les navigateurs - Javascript (au moins s'il prétend être compatible avec la spécification ECMAscript) est Javascript; Array, prototype, slice et call sont toutes des fonctionnalités du langage de base + types d'objet.
Jason S
6
mais ils ne peuvent pas être utilisés sur NodeLists dans IE (je sais que ça craint, mais bon voir ma mise à jour)
gblazex
9
parce que les NodeLists ne font pas partie du langage, ils font partie de l'API DOM, qui est connue pour être boguée / imprévisible, en particulier dans IE
gblazex
3
Array.prototype.slice n'est pas multi-navigateur, si vous prenez en compte IE8.
Lajos Meszaros
1
Oui, c'est essentiellement ma réponse :) Bien que ce soit plus pertinent en 2010 qu'aujourd'hui (2015).
gblazex
224

Avec ES6, nous avons maintenant un moyen simple de créer un tableau à partir d'une NodeList: la Array.from()fonction.

// nl is a NodeList
let myArray = Array.from(nl)
webdif
la source
comment cette nouvelle méthode ES6 se compare-t-elle en vitesse aux autres qui ont été mentionnées ci-dessus?
user5508297
10
@ user5508297 Mieux que l'astuce d'appel de tranche. Plus lent que les boucles for, mais ce n'est pas exactement que nous pouvons vouloir avoir un tableau sans le traverser. Et la syntaxe est belle, simple et facile à retenir!
webdif
Une bonne chose à propos d'Array.from est que vous pouvez utiliser l'argument de la carte:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde
103

Voici une nouvelle façon sympa de le faire en utilisant l' opérateur de propagation ES6 :

let arr = [...nl];
Alexander Olsson
la source
7
N'a pas fonctionné pour moi en dactylographie. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian, le
1
Arrive en 3e plus rapide, derrière les 3 méthodes de changement de
vitesse
19

Quelques optimisations:

  • enregistrer la longueur de la NodeList dans une variable
  • définissez explicitement la longueur du nouveau tableau avant de définir.
  • accéder aux indices, plutôt que de pousser ou de déplacer.

Code ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
thaïlandais
la source
Vous pouvez gagner un peu plus de temps en utilisant Array (length) plutôt qu'en créant le tableau et en le dimensionnant séparément. Si vous utilisez ensuite const pour déclarer le tableau et sa longueur, avec un let à l'intérieur de la boucle, il se termine environ 1,5% plus rapidement que la méthode ci-dessus: const a = Array (nl.length), c = a.length; pour (soit b = 0; b <c; b ++) {a [b] = nl [b]; } voir jsperf.com/nodelist-to-array/93
BrianFreud
15

Les résultats dépendront complètement du navigateur, pour donner un verdict objectif, nous devons faire des tests de performances, voici quelques résultats, vous pouvez les exécuter ici :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

Aperçu de la plateforme IE9 3:

CMS
la source
1
Je me demande comment l'inverse de la boucle résiste à ces ... for (var i=o.length; i--;)... la "boucle" de ces tests a-t-elle réévalué la propriété length à chaque itération?
Dagg Nabbit
14

Le navigateur le plus rapide et le plus croisé est

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

Comme je l'ai comparé dans

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

* Merci @CMS pour l'idée!

Chrome (similaire à Google Chrome) Firefox Opéra

Felipe Buccioni
la source
1
le lien semble être faux, devrait être 91, au lieu de 89 pour inclure le test que vous mentionnez. Et 98 semble le plus complet.
Yaroslav Yakovlev
9

Dans ES6, vous pouvez utiliser:

  • Array.from

    let array = Array.from(nodelist)

  • Opérateur de diffusion

    let array = [...nodelist]

Isabelle
la source
N'ajoute rien de nouveau à ce qui a déjà été écrit dans les réponses précédentes.
Stefan Becker
7
NodeList.prototype.forEach = Array.prototype.forEach;

Vous pouvez maintenant faire document.querySelectorAll ('div'). ForEach (function () ...)

John Williams
la source
Bonne idée, merci @John! Cependant, NodeListne fonctionne pas mais Objectest: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell
3
N'utilisez pas Object.prototype: il casse JQuery et une tonne de choses comme la syntaxe littérale du dictionnaire.
Nate Symer
Bien sûr, évitez d'étendre les fonctions natives intégrées.
roland
5

plus rapide et plus court:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
anonyme
la source
3
Pourquoi >>>0? Et pourquoi ne pas mettre les affectations sur la première partie de la boucle for?
Camilo Martin
5
C'est aussi un buggy. Quand lest 0, la boucle se terminera, donc le 0e élément ne sera pas copié (rappelez-vous qu'il y a un élément à l'index 0)
Camilo Martin
1
J'adore cette réponse, mais ... Tous ceux qui se demandent: le >>> peut ne pas être nécessaire ici mais est utilisé pour garantir que la longueur de la liste de nodules adhère aux spécifications du tableau; il garantit qu'il s'agit d'un entier 32 bits non signé. Vérifiez-le ici ecma-international.org/ecma-262/5.1/#sec-15.4 Si vous aimez le code illisible, utilisez cette méthode avec les suggestions de @ CamiloMartin!
Todd
En réponse à @CamiloMartin - Il est risqué de mettre varà l'intérieur la première partie d'une forboucle à cause du «levage variable». La déclaration de varsera «hissée» en haut de la fonction, même si la varligne apparaît quelque part plus bas, ce qui peut provoquer des effets secondaires qui ne sont pas évidents dans la séquence de code. Par exemple un code dans la même fonction se produisant avant la boucle pourrait dépendre de aet létant non déclarés. Par conséquent, pour une meilleure prévisibilité, déclarez vos vars en haut de la fonction (ou si sur ES6, utilisez constou à la letplace, qui ne sont pas hissés).
brennanyoung
3

Consultez cet article de blog ici qui parle de la même chose. D'après ce que je comprends, le temps supplémentaire pourrait avoir à voir avec la montée de la chaîne de portée.

Vivin Paliath
la source
Intéressant. Je viens de faire des tests similaires maintenant et Firefox 3.6.3 ne montre aucune augmentation de la vitesse dans les deux sens, tandis qu'Opera 10.6 a une augmentation de 20% et Chrome 6 a une augmentation de 230% (!) En le faisant de manière itérative manuelle.
jairajs89
@ jairajs89 assez étrange. Il semble que le Array.prototype.slicesoit dépendant du navigateur. Je me demande quel algorithme chacun des navigateurs utilise.
Vivin Paliath
3

C'est la fonction que j'utilise dans mon JS:

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

Voici les graphiques mis à jour à la date de cette publication (le graphique "plate-forme inconnue" est Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

D'après ces résultats, il semble que la méthode de préallocation 1 soit le pari multi-navigateur le plus sûr.

TomSlick
la source
1

En supposant nodeList = document.querySelectorAll("div")qu'il s'agit d'une forme concise de conversion nodelisten tableau.

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

Voyez-moi l'utiliser ici .

Udo E.
la source