Wie konvertiere ich eine DOM-Knotenliste in ein Array in Javascript?

95

Ich habe eine Javascript-Funktion, die eine Liste von HTML-Knoten akzeptiert, aber ein Javascript-Array erwartet (es werden einige Array-Methoden ausgeführt), und ich möchte ihr die Ausgabe zuführen, die Document.getElementsByTagNameeine DOM-Knotenliste zurückgibt.

Anfangs dachte ich daran, etwas Einfaches zu verwenden wie:

Array.prototype.slice.call(list,0)

Und das funktioniert in allen Browsern einwandfrei, außer natürlich Internet Explorer, der den Fehler "JScript-Objekt erwartet" zurückgibt, da die von Document.getElement*Methoden zurückgegebene DOM-Knotenliste anscheinend kein JScript-Objekt ist, das ausreicht, um das Ziel eines Funktionsaufrufs zu sein.

Vorsichtsmaßnahmen: Es macht mir nichts aus, Internet Explorer-spezifischen Code zu schreiben, aber ich darf keine Javascript-Bibliotheken wie JQuery verwenden, da ich ein Widget schreibe, das in die Website eines Drittanbieters eingebettet werden soll, und ich kann keine externen Bibliotheken laden schafft Konflikte für die Kunden.

Meine letzte Anstrengung besteht darin, die DOM-Knotenliste zu durchlaufen und selbst ein Array zu erstellen. Aber gibt es eine bessere Möglichkeit, dies zu tun?

Guss
quelle
Besser noch, erstellen Sie eine Funktion zum Konvertieren aus der DOM-Knotenliste, aber das wäre wirklich meine Lösung. Ich denke, Sie haben es richtig gemacht.
Kristoffer Sall-Storgaard
> for (i = 0; i & lt; x.length; i ++) Warum wird bei jeder Iteration die Länge der NodeList ermittelt? Es ist nicht nur Zeitverschwendung, sondern da es sich bei NodeLists um Live-Sammlungen handelt, können Sie endlose Schleifen erstellen oder einen Index außerhalb der Grenzen treffen, wenn irgendetwas im Hauptteil der Schleife seine Länge ändert. Letzteres ist das Schlimmste, was passieren kann, wenn Sie einer Variablen die Länge zuweisen, und ein Fehler ist viel besser als eine Endlosschleife.
Dies ist eine sehr alte Frage, aber jQuery wurde speziell mit der .noConflict- Methode erstellt, damit es nicht zu Konflikten mit anderen Bibliotheken (auch nicht selbst) kommt. Dies bedeutet, dass mehrere Versionen von jQuery auf eine Seite geladen werden können. Das heißt, es ist am besten zu vermeiden, eine Bibliothek zu verwenden / zu laden, es sei denn, Sie müssen unbedingt.
Vol7ron
@ vol7ron: Schneller Vorlauf bis 2016, und alle sind immer noch gespannt auf die Größe, die Javascript-Bibliotheken der Seite hinzufügen. Zugegeben, JQuery minimiert und komprimiert ist 30 KB, es sind immer noch 30 KB zu viel, um eine Knotenliste zu transformieren :-)
Guss

Antworten:

64

Nodelisten sind Host - Objekte , die unter Verwendung Array.prototype.slicewird Verfahren auf Host - Objekten nicht garantiert werden , stellt die ECMAScript - Spezifikation:

Ob die Slice-Funktion erfolgreich auf ein Host-Objekt angewendet werden kann, hängt von der Implementierung ab.

Ich würde Ihnen empfehlen, eine einfache Funktion zu erstellen, um das zu durchlaufen NodeListund jedes vorhandene Element einem Array hinzuzufügen:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

AKTUALISIEREN:

Wie andere Antworten vermuten lassen, können Sie jetzt in modernen Umgebungen die Spread-Syntax oder die Array.fromMethode verwenden:

const array = [ ...nodeList ] // or Array.from(nodeList)

Aber wenn ich darüber nachdenke, denke ich, dass der häufigste Anwendungsfall zum Konvertieren einer NodeList in ein Array darin besteht, darüber zu iterieren, und jetzt hat das NodeList.prototypeObjekt die forEachMethode nativ . Wenn Sie sich also in einer modernen Umgebung befinden, können Sie sie direkt verwenden oder haben eine Pollyfill.

CMS
quelle
2
Dies erstellt ein Array mit der ursprünglichen Reihenfolge der Liste in umgekehrter Reihenfolge, was meiner Meinung nach nicht das ist, was das OP will. Wolltest du array[i] = obj[i]stattdessen tun array.push(obj[i])?
Tim Down
@ Tim, richtig, ich hatte es schon einmal so, aber gestern Abend bearbeitet, ohne es zu merken (3 Uhr Ortszeit :), Danke!.
CMS
9
Unter welchen Umständen wäre obj.lengthetwas anderes als ein ganzzahliger Wert?
Peter
1
Ich kann nicht glauben, dass es so kompliziert ist. Hässlich. Dies ist ein sehr häufiger Bedarf bei der Web / JS-Programmierung. Eine neue Methode für die nächste Sprachversion?
Andrew Koper
1
@ AlbertoPerez, du bist willkommen!. Saludos hasta Madrid!
CMS
124

In es6 können Sie einfach Folgendes verwenden:

  • Spread-Operator

     var elements = [... nodelist]
  • Verwenden von Array.from

     var elements = Array.from(nodelist)

Weitere Informationen finden Sie unter https://developer.mozilla.org/en-US/docs/Web/API/NodeList

Camiloazula
quelle
4
so einfach mit Array.from(): D
Josan Iracheta
4
Falls jemand diesen Ansatz mit Typescript (auf ES5) verwendet, Array.fromfunktioniert dies nur , da TS dies auf transpiliert nodelist.slice- was nicht unterstützt wird.
Peter Albert
Ich habe das gleiche ein Jahr vor dir geantwortet und du hast mir die Stimmen gegeben? Ich kann das nicht erklären ..
vsync
3
@vsync, Ihre Antwort erwähnt nichtArray.from
ESR
@EdmundReed - also? Wie rechtfertigt das das? Es ist länger zu schreiben, so dass es in der realen Situation niemals verwendet werden kann, sondern nur spreadverwendet wird.
vsync
16

Mit Spread (ES2015) ist es so einfach wie:[...document.querySelectorAll('p')]

(Optional: Verwenden Sie Babel, um den obigen ES6-Code in die ES5-Syntax zu transpilieren.)


Probieren Sie es in der Konsole Ihres Browsers aus und sehen Sie die Magie:

for( links of [...document.links] )
  console.log(links);
vsync
quelle
Zumindest spätestens Chrom, 44, bekomme ich folgendes: Uncaught TypeError: document.querySelectorAll ist keine Funktion (…)
Nick
@OmidHezaveh - Wie gesagt, dies ist ES6-Code. Ich weiß nicht, ob Chrome 44 ES6 unterstützt und wenn ja, bei welcher Abdeckung. Es ist ein fast ein Jahr alter Browser, und natürlich müssten Sie diesen Code in einem Browser ausführen, der die ES6-Verbreitung unterstützt.
vsync
Oder transpile es ES5 vor der Ausführung
Hello World
8

Verwenden Sie diesen einfachen Trick

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})
Gena Shumilkin
quelle
Können Sie bitte erklären, warum dies Ihrer Meinung nach bessere Erfolgschancen hat als die Verwendung Array.prototype.slice(oder [].slicewie Sie sagen)? Als Hinweis möchte ich kommentieren, dass der IE-spezifische Fehler, den ich im Q dokumentiert habe, in IE 8 oder niedriger auftritt, wo er mapsowieso nicht implementiert ist. In IE 9 ("Standardmodus") oder höher sind beide sliceund mapauf die gleiche Weise erfolgreich.
Guss
6

Obwohl es sich nicht wirklich um eine richtige Unterlegscheibe handelt, da es keine Spezifikation gibt, die das Arbeiten mit DOM-Elementen erfordert, habe ich eine erstellt, die Sie auf slice()diese Weise verwenden können: https://gist.github.com/brettz9/6093105

UPDATE : Als ich dies mit dem Editor der DOM4-Spezifikation angesprochen habe (mit der Frage, ob sie Host-Objekten ihre eigenen Einschränkungen hinzufügen könnten (damit die Spezifikation erfordert, dass Implementierer diese Objekte ordnungsgemäß konvertieren, wenn sie mit Array-Methoden verwendet werden), die über die ECMAScript-Spezifikation hinausgehen Er antwortete, dass "Host-Objekte gemäß ES6 / IDL mehr oder weniger veraltet sind". Ich sehe per http://www.w3.org/TR/WebIDL/#es-array, dass Spezifikationen diese IDL verwenden können, um "Plattform-Array-Objekte" zu definieren, aber http://www.w3.org/TR/domcore/ nicht Es scheint, dass die neue IDL nicht für verwendet wird HTMLCollection(obwohl es so aussieht, als ob dies der Fall wäre, Element.attributesobwohl nur explizit angegeben wird, dass WebIDL für DOMString und DOMTimeStamp verwendet wird). Ich verstehe[ArrayClass](das von Array.prototype erbt) wird für verwendet NodeList(und NamedNodeMapist jetzt zugunsten des einzigen Elements, das es noch verwenden würde, veraltet Element.attributes). In jedem Fall sieht es so aus, als ob es Standard werden soll. Der ES6 ist Array.frommöglicherweise auch für solche Konvertierungen praktischer als die Angabe Array.prototype.sliceund semantisch klarer als [].slice()(und die kürzere Form Array.slice()(ein "Array-Generikum") ist meines Wissens nicht zum Standardverhalten geworden).

Brett Zamir
quelle
Ich habe aktualisiert, um anzuzeigen, dass sich die Spezifikationen möglicherweise in die Richtung bewegen, in die dieses Verhalten erforderlich ist.
Brett Zamir
5

Heute, im Jahr 2018, könnten wir ECMAScript 2015 (6. Ausgabe) oder ES6 verwenden, aber nicht alle Browser können es verstehen (z. B. versteht IE nicht alles). Wenn Sie möchten, können Sie ES6 wie folgt verwenden: var array = [... NodeList];( als Spread-Operator ) oder var array = Array.from(NodeList);.

In einem anderen Fall (wenn Sie ES6 nicht verwenden können) können Sie den kürzesten Weg verwenden, um a NodeListin ein zu konvertieren Array:

var array = [].slice.call(NodeList, 0);.

Beispielsweise:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Aber wenn Sie die iterieren DOMnur leicht Knotenliste, dann brauchen Sie nicht ein konvertieren , NodeListum eine Array. Es ist möglich, die Elemente in einer NodeListVerwendung zu durchlaufen:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Versuchen Sie nicht, die Elemente in der Liste zu verwenden for...inoder for each...inaufzulisten, da dies auch die Länge und die Elementeigenschaften von auflistet NodeListund Fehler verursacht, wenn Ihr Skript davon ausgeht, dass es sich nur um Elementobjekte handelt. Es for..inist auch nicht garantiert, die Immobilien in einer bestimmten Reihenfolge zu besuchen. for...ofSchleifen durchlaufen NodeList-Objekte korrekt.

Auch sehen:

Bharata
quelle
3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

Dies sollte funktionieren, browserübergreifend sein und Ihnen alle "Element" -Knoten bringen.

Strelok
quelle
1
Dies ist im Grunde die gleiche Antwort wie bei @ CMS, außer dass davon ausgegangen wird, dass ich nur Elementknoten möchte - was ich nicht tue.
Guss