Najbardziej efektywny sposób konwersji HTMLCollection na macierz

391

Czy istnieje bardziej skuteczny sposób konwersji HTMLCollection na macierz, inny niż iteracja przez zawartość wspomnianej kolekcji i ręczne wpychanie każdego elementu do tablicy?

Tomek
źródło
10
Co należy rozumieć przez „wydajny”? Jeśli najlepiej się sprawdza, pętla for jest na ogół szybsza niż Array.prototype.slice . Pętla działa również w wielu różnych przeglądarkach (tj. We wszystkich), więc według tych kryteriów jest to „najbardziej wydajny sposób”. I to jest bardzo mały kod: for (var a=[], i=collection.length; i;) a[--i] = collection[i];tak mało „
oszustwa
@RobG Dziękuję - dałbym + 59k, gdybym mógł! ;-)
Slashback
1
Patrząc na obecną wydajność przeglądarki , plasterek głównie dogania pętle pod względem wydajności, z wyjątkiem Chrome. Przy użyciu większej liczby elementów i niewielkiej optymalizacji pętli wyniki są prawie identyczne , z wyjątkiem Chrome, w którym pętla jest znacznie szybsza.
RobG
Stworzyłem test jsperf, który analizuje obie metody wspomniane przez @harpo, a także test jquery wydajności. Odkryłem, że jquery jest nieco wolniejsze niż obie metody javascript, a najwyższa wydajność różni się w zależności od przypadków testowych js. Chrome 59.0.3071 / Mac OS X 10.12.5 woli używać, Array.prototype.slice.calla Brave (oparty na Chrome 59.0.3071) praktycznie nie ma różnicy między dwoma testami javascript w wielu uruchomieniach. Zobacz jsperf.com/htmlcollection-array-vs-jquery-children
NuclearPeon
jsben.ch/h2IFA => test wydajności najczęstszych sposobów na to
EscapeNetscape

Odpowiedzi:

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

będzie miał ten sam efekt przy użyciu „natywnego” kodu.

Edytować

Ponieważ uzyskuje się wiele wyświetleń, zwróć uwagę (na komentarz @ oriol), że następujące bardziej zwięzłe wyrażenie jest faktycznie równoważne:

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

Zwróć jednak uwagę na komentarz @ JussiR, że w przeciwieństwie do formy „pełnej”, tworzy w tym procesie pustą, nieużywaną i rzeczywiście nieużywaną instancję tablicy. To, co robią kompilatory, jest poza zasięgiem programisty.

Edytować

Od ECMAScript 2015 (ES 6) istnieje również Array. Od :

var arr = Array.from(htmlCollection);

Edytować

ECMAScript 2015 udostępnia również operator rozkładania , który jest funkcjonalnie równoważny Array.from(chociaż uwaga, która Array.fromobsługuje funkcję odwzorowania jako drugi argument).

var arr = [...htmlCollection];

Potwierdziłem, że obie powyższe prace działają NodeList.

Porównanie wydajności wymienionych metod: http://jsben.ch/h2IFA

harpo
źródło
7
Nie udaje się to w IE6.
Heath Borders
29
Skrót [].slice.call(htmlCollection)również działa.
Oriol
1
@ChrisNielsen Tak. Zostałem o tym źle poinformowany. Przepraszam za rozpowszechnianie tego. Nie zdawałem sobie sprawy, że też to stwierdziłem. Usunąłem komentarz, aby uniknąć pomyłek, ale dla kontekstu przeczytałem (lub źle przeczytałem) gdzieś, że pocięcie HTMLCollection sprawiło, że zachowuje się on zarówno jako tablica, jak i kolekcja. Całkowicie niepoprawny.
Erik Reppen
3
Skrót [] .slice nie jest równoważny, ponieważ tworzy również nieużywaną pustą instancję tablicy. Nie jestem jednak pewien, czy kompilatory są w stanie go zoptymalizować.
JussiR
3
Array.from, tzn. fromnie jest obsługiwany przez IE11.
Frank Conijn
86

nie jestem pewien, czy jest to najbardziej wydajny, ale zwięzła składnia ES6 może wyglądać tak:

let arry = [...htmlCollection] 

Edycja: Kolejna, z komentarza Chris_F:

let arry = Array.from(htmlCollection)
mido
źródło
9
Dodatkowo ES6 dodajeArray.from()
Chris_F
4
Uważaj na pierwszy, podczas transpozycji z babel występuje subtelny błąd, w którym [... htmlCollection] zwróci tablicę z htmlCollection, ponieważ jest to jedyny element.
Marcel M.,
3
Operator rozprzestrzeniania się macierzy nie działa na htmlCollection. Ma zastosowanie tylko do NodeList.
Bobby,
1
Array.from, tzn. fromnie jest obsługiwany przez IE11.
Frank Conijn
Benchmark Wygląda na to, że operator spreadu jest szybszy z tych 2.
RedSparr0w
20

Widziałem bardziej zwięzłą metodę pozyskiwania Array.prototypemetod, która działa równie dobrze. Przekształcanie HTMLCollectionobiektu w Arrayobiekt pokazano poniżej:

[] .slice.call (yourHTMLCollectionObject);

I, jak wspomniano w komentarzach, w przypadku starszych przeglądarek, takich jak IE7 i wcześniejsze, wystarczy użyć funkcji zgodności, takiej jak:

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

    return a
}

Wiem, że to stare pytanie, ale czułem, że zaakceptowana odpowiedź była nieco niepełna; więc pomyślałem, że wyrzucę to FWIW.

Codesmith
źródło
6

W przypadku implementacji w różnych przeglądarkach sugeruję, abyś spojrzał na funkcję prototype.js $A

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

Prawdopodobnie nie używa, Array.prototype.sliceponieważ nie jest dostępny w każdej przeglądarce. Obawiam się, że wydajność jest dość zła, ponieważ istnieje odwrócenie pętli javascript nad iterable.

Gareth Davis
źródło
2
OP poprosił o inny sposób niż „iterowanie po zawartości wspomnianej kolekcji i ręczne wpychanie każdego elementu do tablicy”, ale to właśnie ta $Afunkcja wykonuje większość czasu.
Luc125
1
Myślę, że próbowałem zrobić to, że nie ma dobrego sposobu, aby to zrobić, kod prototype.js pokazuje, że możesz poszukać metody „toArray”, ale w przypadku braku tej iteracji najbezpieczniejsza trasa
Gareth Davis
1
Spowoduje to utworzenie nowych, nieokreślonych członków w rzadkich tablicach. Przed przypisaniem powinien istnieć test hasOwnProperty .
RobG
3

To jest moje osobiste rozwiązanie, oparte na informacjach tutaj (ten wątek):

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

Gdzie $ A został opisany przez Garetha Davisa w jego poście:

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

Jeśli przeglądarka obsługuje najlepszy sposób, ok, w przeciwnym razie będzie korzystać z przeglądarki krzyżowej.

Gustavo
źródło
Ogólnie rzecz biorąc, nie oczekuję, że try / catch będzie skutecznym sposobem zarządzania przepływem kontroli. Możesz sprawdzić, czy funkcja istnieje najpierw, a następnie uruchomić jedną lub drugą nieco taniej.
Patrick,
2
Podobnie jak w przypadku odpowiedzi Garetha Davisa, tworzy to nowych, nieokreślonych członków w rzadkich tablicach, tak się [,,]dzieje [undefined, undefined].
RobG
Nie miałem jeszcze takich problemów. Wygląda na to, że kolekcja 3 elementów daje tablicę z 2 elementami. Jeśli chodzi o puste, to jest niezdefiniowane, to jest trochę ograniczeń JavaScript, domyślam się, że spodziewałeś się wartości null zamiast niezdefiniowanej, prawda?
Gustavo,
3

Działa to we wszystkich przeglądarkach, w tym we wcześniejszych wersjach IE.

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

Ponieważ jsperf jest obecnie niedostępny, oto jsfiddle, który porównuje wydajność różnych metod. https://jsfiddle.net/qw9qf48j/

Mikołaj
źródło
spróbujvar args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
Shahar Shokrani,
3

Aby skutecznie przekonwertować tablicę na tablicę, możemy skorzystać z jQuery makeArray :

makeArray: Konwertuj obiekt podobny do tablicy na prawdziwą tablicę JavaScript.

Stosowanie:

var domArray = jQuery.makeArray(htmlCollection);

Trochę więcej:

Jeśli nie chcesz zachować odwołania do obiektu tablicy (przez większość czasu HTMLCollections zmieniają się dynamicznie, więc lepiej skopiować je do innej tablicy, w tym przykładzie zwróć szczególną uwagę na wydajność:

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

Co to jest tablica?

HTMLCollection jest "array-like"przedmiotem, array-jak obiekty są podobne do obiektu Array, ale brakuje wiele jego definicji funkcjonalnym:

Obiekty podobne do tablic wyglądają jak tablice. Mają różne numerowane elementy i właściwość długości. Ale tam kończy się podobieństwo. Obiekty podobne do macierzy nie mają żadnych funkcji macierzy, a pętle wejściowe nawet nie działają!

Shahar Shokrani
źródło