Die effizienteste Methode zum Konvertieren einer HTMLCollection in ein Array

391

Gibt es eine effizientere Möglichkeit, eine HTMLCollection in ein Array zu konvertieren, als den Inhalt dieser Sammlung zu durchlaufen und jedes Element manuell in ein Array zu verschieben?

Tom
quelle
10
Was ist mit "effizient" gemeint? Bei bester Leistung ist eine for- Schleife im Allgemeinen schneller als Array.prototype.slice . Eine Schleife funktioniert auch in einer größeren Anzahl von Browsern (dh allen). Nach diesen Kriterien ist sie der "effizienteste Weg". Und es ist sehr wenig Code: for (var a=[], i=collection.length; i;) a[--i] = collection[i];also nicht viel von einem "
Betrug
@RobG Danke - ich würde dir + 59k geben, wenn ich könnte! ;-)
Slashback
1
Mit Blick auf die aktuelle Browserleistung hat Slice die Schleifen in Bezug auf die Leistung größtenteils eingeholt, außer in Chrome. Bei Verwendung einer größeren Anzahl von Elementen und einer geringfügigen Optimierung der Schleife sind die Ergebnisse nahezu identisch , außer in Chrome, wo eine Schleife sehr viel schneller ist.
RobG
Ich habe einen jsperf-Test erstellt, der sowohl die von @harpo erwähnten Methoden als auch einen jquery-Test für die Leistung untersucht. Ich habe festgestellt, dass jquery etwas langsamer ist als beide Javascript-Methoden und die Spitzenleistung zwischen den js-Testfällen variiert. Chrome 59.0.3071 / Mac OS X 10.12.5 bevorzugt die Verwendung Array.prototype.slice.callund Brave (basierend auf Chrome 59.0.3071) unterscheidet praktisch nicht zwischen den beiden Javascript-Tests über mehrere Läufe. Siehe jsperf.com/htmlcollection-array-vs-jquery-children
NuclearPeon
jsben.ch/h2IFA => Leistungstest für die gebräuchlichsten Methoden
EscapeNetscape

Antworten:

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

hat den gleichen Effekt mit "nativem" Code.

Bearbeiten

Da dies viele Ansichten erhält, beachten Sie (per @ oriols Kommentar), dass der folgende präzisere Ausdruck effektiv äquivalent ist:

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

Beachten Sie jedoch gemäß dem Kommentar von @ JussiR, dass im Gegensatz zum "ausführlichen" Formular eine leere, nicht verwendete und tatsächlich unbrauchbare Array-Instanz erstellt wird. Was Compiler dagegen tun, liegt außerhalb des Ken des Programmierers.

Bearbeiten

Seit ECMAScript 2015 (ES 6) gibt es auch Array.from :

var arr = Array.from(htmlCollection);

Bearbeiten

ECMAScript 2015 bietet auch den Spread-Operator , der funktional äquivalent zu ist Array.from(obwohl beachten Sie, dass Array.fromeine Zuordnungsfunktion als zweites Argument unterstützt wird).

var arr = [...htmlCollection];

Ich habe bestätigt, dass beide oben genannten Arbeiten funktionieren NodeList.

Ein Leistungsvergleich für die genannten Methoden: http://jsben.ch/h2IFA

Harpo
quelle
7
Dies schlägt in IE6 fehl.
Heath Borders
29
Die Verknüpfung [].slice.call(htmlCollection)funktioniert auch.
Oriol
1
@ChrisNielsen Ja, ich wurde darüber falsch informiert. Tut mir leid, dass ich das verbreitet habe. Ich wusste nicht, dass ich das auch hier angegeben hatte. Der Kommentar wurde gelöscht, um Verwirrung zu vermeiden, aber für den Kontext hatte ich irgendwo gelesen (oder falsch gelesen), dass das Schneiden einer HTMLCollection dazu führte, dass sie sich sowohl wie ein Array als auch wie eine Sammlung verhält. Völlig falsch.
Erik Reppen
3
Die Verknüpfung [] .slice ist nicht äquivalent, da auch nicht verwendete leere Array-Instanzen erstellt werden. Ich bin mir jedoch nicht sicher, ob Compiler es wegoptimieren können.
JussiR
3
Array.fromdh fromwird von IE11 nicht unterstützt.
Frank Conijn
86

Ich bin mir nicht sicher, ob dies die effizienteste ist, aber eine präzise ES6-Syntax könnte sein:

let arry = [...htmlCollection] 

Edit: Noch einer, aus Chris_F Kommentar:

let arry = Array.from(htmlCollection)
mido
quelle
9
Zusätzlich fügt ES6 hinzuArray.from()
Chris_F
4
Achten Sie auf den ersten, es gibt einen subtilen Fehler beim Transpilieren mit babel, bei dem [... htmlCollection] ein Array mit der htmlCollection als einzigem Element zurückgibt.
Marcel M.
3
Der Array-Spread-Operator funktioniert bei htmlCollection nicht. Dies gilt nur für NodeList.
Bobby
1
Array.fromdh fromwird von IE11 nicht unterstützt.
Frank Conijn
Benchmark Es sieht so aus, als ob der Spread-Operator von diesen 2 schneller ist.
RedSparr0w
20

Ich habe eine präzisere Methode gesehen, um Array.prototypeMethoden im Allgemeinen zu erhalten, die genauso gut funktioniert. Das Konvertieren eines HTMLCollectionObjekts in ein ArrayObjekt wird unten gezeigt:

[] .slice.call (yourHTMLCollectionObject);

Und wie in den Kommentaren erwähnt, müssen Sie für alte Browser wie IE7 und früher einfach eine Kompatibilitätsfunktion verwenden, wie:

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

    return a
}

Ich weiß, dass dies eine alte Frage ist, aber ich fand, dass die akzeptierte Antwort etwas unvollständig war; Also dachte ich, ich würde das hier rauswerfen, FWIW.

Codesmith
quelle
6

Für eine browserübergreifende Implementierung würde ich empfehlen, dass Sie sich die Funktion prototype.js ansehen$A

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

Es wird Array.prototype.slicewahrscheinlich nicht verwendet, da es nicht in jedem Browser verfügbar ist. Ich fürchte, die Leistung ist ziemlich schlecht, da der Fallback eine Javascript-Schleife über dem ist iterable.

Gareth Davis
quelle
2
Das OP fragte nach einem anderen Weg als "den Inhalt dieser Sammlung zu durchlaufen und jedes Element manuell in ein Array zu verschieben", aber genau das macht die $AFunktion die meiste Zeit.
Luc125
1
Ich denke, der Punkt, den ich anstrebte, ist, dass es keinen guten Weg gibt, dies zu tun. Der prototype.js-Code zeigt, dass Sie nach einer 'toArray'-Methode suchen können, diese Iteration jedoch am sichersten ist
Gareth Davis,
1
Dadurch werden neue, undefinierte Elemente in dünn besetzten Arrays erstellt. Vor der Zuweisung sollte ein hasOwnProperty- Test durchgeführt werden.
RobG
3

Dies ist meine persönliche Lösung, basierend auf den Informationen hier (dieser Thread):

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

Wo $ A von Gareth Davis in seinem Beitrag beschrieben wurde:

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

Wenn der Browser den besten Weg unterstützt, ok, sonst wird der Cross-Browser verwendet.

Gustavo
quelle
Im Allgemeinen erwarte ich nicht, dass Try / Catch ein effizienter Weg ist, um den Kontrollfluss zu verwalten. Sie können zuerst überprüfen, ob die Funktion vorhanden ist, und dann entweder die eine oder die andere etwas billiger ausführen.
Patrick
2
Wie bei Gareth Davis' Antwort, schafft diese neue, nicht definierte Mitglieder in Sparse - Arrays, so [,,]wird [undefined, undefined].
RobG
Ich habe diese Art von Ärger noch nicht bekommen. Eine 3-Elemente-Sammlung ergibt ein Array mit 2 Elementen. Was leere undefiniert betrifft, gibt es ein paar JavaScript-Einschränkungen. Ich denke, Sie haben null statt undefiniert erwartet, oder?
Gustavo
3

Dies funktioniert in allen Browsern, einschließlich früherer IE-Versionen.

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

Da jsperf derzeit noch nicht verfügbar ist, finden Sie hier eine jsfiddle, die die Leistung verschiedener Methoden vergleicht. https://jsfiddle.net/qw9qf48j/

Nikolaus
quelle
versuchen Sievar args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
Shahar Shokrani
3

Um Array-like in Array auf effiziente Weise zu konvertieren, können wir die jQuery verwenden makeArray :

makeArray: Konvertiert ein Array-ähnliches Objekt in ein echtes JavaScript-Array.

Verwendungszweck:

var domArray = jQuery.makeArray(htmlCollection);

Ein kleines Extra:

Wenn Sie keinen Verweis auf das Array-Objekt behalten möchten (HTMLCollections werden meistens dynamisch geändert, sodass es besser ist, sie in ein anderes Array zu kopieren. In diesem Beispiel wird die Leistung genau beachtet:

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

Was ist Array-ähnlich?

HTMLCollection ist ein "array-like"Objekt. Die Array-ähnlichen Objekte ähneln dem Objekt des Arrays, es fehlt jedoch ein Großteil seiner funktionalen Definition:

Array-ähnliche Objekte sehen aus wie Arrays. Sie haben verschiedene nummerierte Elemente und eine Längeneigenschaft. Aber hier hört die Ähnlichkeit auf. Array-ähnliche Objekte haben keine der Array-Funktionen, und For-In-Schleifen funktionieren nicht einmal!

Shahar Shokrani
quelle