Najszybszy sposób na konwersję JavaScript NodeList na Array?

251

Wcześniej odpowiedzi na pytania tutaj powiedział, że był to najszybszy sposób:

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

Podczas testów porównawczych w mojej przeglądarce stwierdziłem, że jest on ponad 3 razy wolniejszy niż to:

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

Oba generują tę samą moc wyjściową, ale trudno mi uwierzyć, że moja druga wersja jest najszybszym możliwym sposobem, zwłaszcza że ludzie mówią tutaj inaczej.

Czy to dziwactwo w mojej przeglądarce (Chromium 6)? Czy jest szybszy sposób?

EDYCJA: Dla każdego, kogo to obchodzi, zdecydowałem się na następujące (co wydaje się najszybsze w każdej testowanej przeglądarce):

//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: Znalazłem jeszcze szybszy sposób

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));
jairajs89
źródło
2
arr[arr.length] = nl[i];może być szybszy niż, arr.push(nl[i]);ponieważ pozwala uniknąć wywołania funkcji.
Luc125
9
Ta strona jsPerf śledzi wszystkie odpowiedzi na tej stronie: jsperf.com/nodelist-to-array/27
pilau
Pamiętaj, że „EDIT2: znalazłem szybszy sposób” jest o 92% wolniejszy na IE8.
Camilo Martin
2
Ponieważ wiesz już, ile masz węzłów:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mem
@ Luc125 To zależy od przeglądarki, ponieważ implementacja wypychania może być zoptymalizowana, myślę o chrome, ponieważ v8 jest dobra z tego rodzaju rzeczami.
axelduch

Odpowiedzi:

197

Drugi jest zazwyczaj szybszy w niektórych przeglądarkach, ale najważniejsze jest to, że musisz go używać, ponieważ pierwszy nie jest po prostu przeznaczony dla różnych przeglądarek. Nawet jeśli czasy się zmieniają

@kangax ( podgląd IE 9 )

Array.prototype.slice może teraz konwertować niektóre obiekty hosta (np. NodeList) na tablice - coś, co większość współczesnych przeglądarek była w stanie zrobić od dłuższego czasu.

Przykład:

Array.prototype.slice.call(document.childNodes);
gblazex
źródło
??? Oba są kompatybilne z różnymi przeglądarkami - Javascript (przynajmniej jeśli twierdzi, że jest zgodny ze specyfikacją ECMAscript) to Javascript; Tablica, prototyp, plasterek i wywołanie to cechy podstawowego języka + typy obiektów.
Jason S
6
ale nie można ich używać na NodeLists w IE (wiem, że to do bani, ale hej, zobacz moją aktualizację)
gblazex
9
ponieważ NodeLists nie są częścią języka, są częścią interfejsu API DOM, o którym wiadomo, że jest
błędny
3
Array.prototype.slice nie działa w różnych przeglądarkach, jeśli weźmiesz pod uwagę IE8.
Lajos Meszaros
1
Tak, o to właściwie chodziło w mojej odpowiedzi :) Chociaż była bardziej trafna w 2010 roku niż w dzisiejszym (2015).
gblazex
224

W ES6 mamy teraz prosty sposób na utworzenie tablicy z NodeList: Array.from()funkcję.

// nl is a NodeList
let myArray = Array.from(nl)
webdif
źródło
Jak ta nowa metoda ES6 wypada szybciej niż inne, o których wspomniano powyżej?
user5508297,
10
@ user5508297 Lepszy niż sztuczka wywołania połączenia. Wolniej niż w przypadku pętli for, ale nie do końca możemy chcieć mieć tablicę bez przechodzenia przez nią. A składnia jest piękna, prosta i łatwa do zapamiętania!
webdif
Zaletą Array.from jest to, że można użyć argumentu map:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde
103

Oto nowy fajny sposób na zrobienie tego za pomocą operatora rozprzestrzeniania ES6 :

let arr = [...nl];
Alexander Olsson
źródło
7
Nie działało dla mnie w maszynopisie. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian
1
Zajmuje 3 miejsce najszybciej, za 3 metodami unshift
BrianFreud
19

Niektóre optymalizacje:

  • zapisz długość NodeList w zmiennej
  • jawnie ustaw nową długość tablicy przed ustawieniem.
  • dostęp do indeksów, zamiast wypychania lub cofania.

Kod ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
tajski
źródło
Możesz ogolić się trochę dłużej, używając tablicy (długości) zamiast tworzyć tablicę, a następnie osobno ją skalować. Jeśli następnie użyjesz const do zadeklarowania tablicy i jej długości, z letem wewnątrz pętli, kończy się ona około 1,5% szybciej niż powyższa metoda: const a = Array (nl.length), c = a.length; dla (niech b = 0; b <c; b ++) {a [b] = nl [b]; } patrz jsperf.com/nodelist-to-array/93
BrianFreud
15

Wyniki będą całkowicie zależeć od przeglądarki, aby dać obiektywny werdykt, musimy wykonać testy wydajności, oto kilka wyników, które możesz uruchomić tutaj :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

Przegląd platformy IE9 3:

CMS
źródło
1
Zastanawiam się, jak odwrotna pętla for wytrzymuje te ... for (var i=o.length; i--;)... czy „for loop” w tych testach przewartościowała właściwość długości przy każdej iteracji?
Dagg Nabbit
14

Najszybsza i największa przeglądarka to

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

Jak porównałem w

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

* Dzięki @CMS za pomysł!

Chrom (podobny do Google Chrome) Firefox Opera

Felipe Buccioni
źródło
1
link wydaje się być nieprawidłowy, powinien mieć 91 zamiast 89, aby uwzględnić wspomniany test. I 98 wydaje się najbardziej kompletna.
Jarosław Jakowlew
9

W ES6 możesz użyć:

  • Array.from

    let array = Array.from(nodelist)

  • Operator rozsiewu

    let array = [...nodelist]

Izabela
źródło
Nie dodaje nic nowego, co już napisano w poprzednich odpowiedziach.
Stefan Becker
7
NodeList.prototype.forEach = Array.prototype.forEach;

Teraz możesz zrobić document.querySelectorAll ('div'). ForEach (function () ...)

John Williams
źródło
Dobry pomysł, dzięki @John! Jednak NodeListnie działa, ale Object: Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell
3
Nie używaj Object.prototype: łamie JQuery i mnóstwo rzeczy takich jak składnia dosłowna słownika.
Nate Symer
Jasne, unikaj rozszerzania wbudowanych funkcji natywnych.
roland
5

szybciej i krócej:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
anonimowy
źródło
3
Dlaczego >>>0? A dlaczego nie umieścić zadań w pierwszej części pętli for?
Camilo Martin
5
Ponadto jest to błędne. Kiedy ljest 0, pętla się kończy, dlatego 0element th nie zostanie skopiowany (pamiętaj, że element ma indeks 0)
Camilo Martin
1
Uwielbiam tę odpowiedź, ale ... Każdy, kto się zastanawia: >>> może nie być tutaj konieczny, ale służy do zagwarantowania długości listy węzłów zgodnie ze specyfikacją tablicy; zapewnia, że ​​jest to 32-bitowa liczba całkowita bez znaku. Sprawdź tutaj ecma-international.org/ecma-262/5.1/#sec-15.4 Jeśli podoba Ci się nieczytelny kod, użyj tej metody z sugestiami @ CamiloMartin!
Todd
W odpowiedzi na @CamiloMartin - włożenie vardo pierwszej części forpętli jest ryzykowne z powodu „zmiennego podnoszenia”. Deklaracja varzostanie „podniesiona” na górę funkcji, nawet jeśli varlinia pojawi się gdzieś niżej, co może powodować skutki uboczne, które nie są oczywiste z sekwencji kodu. Na przykład niektóre kodu w tej samej funkcji występujący przed dla pętli może zależeć na ai ljest nielegalnej. Dlatego, aby uzyskać większą przewidywalność, zadeklaruj swoje zmienne na górze funkcji (lub jeśli na ES6, użyj constlub letzamiast tego, które nie podnoszą).
brennanyoung
3

Sprawdź ten post na blogu , który mówi o tym samym. Z tego, co zbieram, dodatkowy czas może mieć związek z przejściem w górę łańcucha lunety.

Vivin Paliath
źródło
Ciekawy. Właśnie zrobiłem teraz kilka podobnych testów i Firefox 3.6.3 nie pokazuje wzrostu prędkości, robiąc to w obu kierunkach, podczas gdy Opera 10.6 ma wzrost o 20%, a Chrome 6 ma wzrost o 230% (!), Wykonując to ręcznie.
jairajs89
@ jairajs89 dość dziwne. Wygląda na Array.prototype.sliceto, że zależy od przeglądarki. Zastanawiam się, jakiego algorytmu używa każda z przeglądarek.
Vivin Paliath
3

Z tej funkcji korzystam w JS:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}
Projektant stron internetowych
źródło
1

Oto wykresy zaktualizowane na dzień opublikowania (wykres „nieznanej platformy” to Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

Na podstawie tych wyników wydaje się, że metoda preallocate 1 jest najbezpieczniejszym zakładem dla różnych przeglądarek.

TomSlick
źródło
1

Zakładając nodeList = document.querySelectorAll("div"), że jest to zwięzła forma konwersji nodelistna tablicę.

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

Zobacz mnie, użyj go tutaj .

Udo E.
źródło