Warum hat die Knotenliste nicht für jeden?

90

Ich habe an einem kurzen Skript gearbeitet, um <abbr>den inneren Text der Elemente zu ändern , aber festgestellt, dass nodelistes keine forEachMethode gibt. Ich weiß, dass nodelistdas nicht erbt Array, aber scheint es forEachnicht eine nützliche Methode zu sein? Gibt es eine bestimmte Implementierung Frage , die ich bin mir nicht bewusst , dass verhindert Zugabe forEachzu nodelist?

Hinweis: Mir ist bekannt, dass Dojo und jQuery beide eine forEachForm für ihre Knotenlisten haben. Ich kann es aufgrund von Einschränkungen auch nicht verwenden.

Schlangen und Kaffee
quelle
6
Hallo aus der Zukunft! nodeList hat forEach seit ES6 .
Blaise

Antworten:

94

NodeList hat jetzt forEach () in allen gängigen Browsern

Siehe nodeList forEach () auf MDN .

Ursprüngliche Antwort

Keine dieser Antworten erklärt, warum NodeList nicht von Array erbt, sodass es forEachund alles andere möglich ist .

Die Antwort finden Sie in diesem Diskussions-Thread . Kurz gesagt, es bricht das Web:

Das Problem war Code, der fälschlicherweise angenommen hat, dass die Instanz ein Array in Kombination mit Array.prototype.concat ist.

Es gab einen Fehler in der Google Closure Library, der dazu führte, dass fast alle Google-Apps aufgrund dessen fehlschlugen. Die Bibliothek wurde aktualisiert, sobald dies gefunden wurde, aber es gibt möglicherweise noch Code, der dieselbe falsche Annahme in Kombination mit concat macht.

Das heißt, irgendein Code hat so etwas getan

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

Allerdings concatwird behandeln „echte“ Arrays (nicht Instanceof Array) unterschiedlich von anderen Objekten:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

Das bedeutet, dass der obige Code als xNodeList fehlerhaft war, weil er zuvor den doSomethingElseWith(x)Pfad entlang ging , während er danach den otherArray.concat(x)Pfad entlang ging , was etwas Seltsames tat, da xes sich nicht um ein echtes Array handelte.

Für einige Zeit gab es einen Vorschlag für eine ElementsKlasse, die eine echte Unterklasse von Array war und als "neue NodeList" verwendet werden sollte. Dies wurde jedoch zumindest vorerst aus dem DOM-Standard entfernt , da eine Implementierung aus verschiedenen technischen und spezifikationsbezogenen Gründen noch nicht möglich war.

Domenic
quelle
11
Scheint mir ein schlechter Anruf zu sein. Im Ernst, ich denke, es ist die richtige Entscheidung, ab und zu etwas zu brechen, besonders wenn es bedeutet, dass wir vernünftige APIs für die Zukunft haben. Außerdem ist das Web nicht annähernd eine stabile Plattform, die Leute sind daran
gewöhnt
3
"Breaks the Web"! = "Breaks Google Stuff"
Matt
Warum nicht einfach die forEach-Methode von Array.prototype ausleihen? Anstatt beispielsweise Array als Prototyp hinzuzufügen, führen Sie dies einfach.forEach = Array.prototype.forEach im Konstruktor aus oder implementieren Sie forEach nur für NodeList?
PopKernel
1
Hinweis; Dieses Update NodeList now has forEach() in all major browsersscheint zu implizieren, dass der IE kein wichtiger Browser ist. Hoffentlich gilt das für einige Leute, aber für mich (noch) nicht.
Graham P Heath
57

Du kannst tun

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );
akuhn
quelle
3
Array.prototype.forEach.callkann kurz sein[].forEach.call
CodeBrauer
7
@CodeBrauer: Das verkürzt nicht nur Array.prototype.forEach.call, es erstellt ein leeres Array und verwendet dessen forEachMethode.
Paul D. Waite
34

Sie können ein neues Array von Knoten erstellen.

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

Hinweis: Dies ist nur eine Liste / ein Array von Knotenreferenzen, die wir hier erstellen, keine doppelten Knoten.

  nodes[0] === nodeList[0] // will be true
sbr
quelle
22
Oder einfach tun Array.prototype.forEach.call(nodeList, fun).
Akuhn
Ich würde auch vorschlagen, die forEach-Funktion so zu aliasen, dass : var forEach = Array.prototype.forEach.call(nodeList, callback);. Jetzt können Sie einfach anrufenforEach(nodeList, callback);
Andrew Craswell
19

Sag niemals nie, es ist 2016 und das NodeListObjekt hat eine forEachMethode in neuestem Chrome (v52.0.2743.116) implementiert.

Es ist zu früh, um es in der Produktion zu verwenden, da andere Browser dies noch nicht unterstützen (getestet FF 49), aber ich würde vermuten, dass dies bald standardisiert wird.

maioman
quelle
2
Opera unterstützt dies ebenfalls und die Unterstützung wird in Version 50 von Firefox hinzugefügt, die am 15.11.16 veröffentlicht werden soll.
Shaggy
1
Obwohl implementiert, ist es nicht Teil eines Standards. Es ist immer noch am besten, dies zu tun, Array.prototype.slice.call(nodelist).forEach(…)was Standard ist und in alten Browsern funktioniert.
Nate
17

Kurz gesagt, es ist ein Designkonflikt, diese Methode zu implementieren.

Von MDN:

Warum kann ich forEach oder map nicht in einer NodeList verwenden?

NodeList werden sehr ähnlich wie Arrays verwendet, und es wäre verlockend, Array.prototype-Methoden für sie zu verwenden. Dies ist jedoch unmöglich.

JavaScript verfügt über einen Vererbungsmechanismus, der auf Prototypen basiert. Array-Instanzen erben Array-Methoden (z. B. forEach oder map), da ihre Prototypkette wie folgt aussieht:

myArray --> Array.prototype --> Object.prototype --> null (Die Prototypkette eines Objekts kann durch mehrmaliges Aufrufen von Object.getPrototypeOf abgerufen werden.)

forEach, map und dergleichen sind eigene Eigenschaften des Array.prototype-Objekts.

Im Gegensatz zu Arrays sieht die NodeList-Prototypkette wie folgt aus:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype enthält die item-Methode, jedoch keine der Array.prototype-Methoden, sodass sie nicht für NodeLists verwendet werden können.

Quelle: https://developer.mozilla.org/en-US/docs/DOM/NodeList (scrollen Sie nach unten zu Warum kann ich forEach oder Map nicht auf einer NodeList verwenden? )

Matt Lo
quelle
8
Warum ist es so gestaltet, da es sich um eine Liste handelt? Was hielt sie davon ab, eine Kette zu machen myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null?
Igor Pantović
14

Wenn Sie forEach in NodeList verwenden möchten, kopieren Sie diese Funktion einfach aus dem Array:

NodeList.prototype.forEach = Array.prototype.forEach;

Das ist alles, jetzt können Sie es auf die gleiche Weise verwenden, wie Sie es für Array tun würden:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
AlexTR
quelle
4
Abgesehen davon, dass das Mutieren der Basisklassen für den durchschnittlichen Leser nicht sehr explizit ist. Mit anderen Worten, tief in einem Code müssen Sie sich jede Anpassung merken, die Sie an den Basisobjekten des Browsers vornehmen. Das Verlassen auf die MDN-Dokumentation ist nicht mehr sinnvoll, da die Objekte ihr Verhalten gegenüber der Norm geändert haben. Es ist besser, den Prototyp zum Zeitpunkt des Anrufs explizit anzuwenden, damit der Leser leicht erkennen kann, dass forEach eine geliehene Idee ist und nicht Teil der Sprachdefinition. Siehe die Antwort, die @akuhn oben hat.
Sukima
@Sukima durch falsche Prämissen fehlgeleitet. In diesem speziellen Fall behebt der angegebene Ansatz, dass sich NodeList wie vom Entwickler erwartet verhält. Dies ist der beste Weg, um das Problem mit der Systemklasse zu beheben. (NodeList scheint unvollendet zu sein und sollte in zukünftigen Versionen der Sprache behoben werden.)
Daniel Garmoshka
1
Jetzt, wo NodeList.forEach existiert, wird dies zu einer unglaublich einfachen Polyfüllung!
Damon
7

In ES2015 können Sie jetzt die forEachMethode für die Knotenliste verwenden.

document.querySelectorAll('abbr').forEach( el => console.log(el));

Siehe Der MDN-Link

Wenn Sie jedoch HTML-Sammlungen oder andere Array-ähnliche Objekte verwenden möchten, können Sie in es2015 die Array.from()Methode verwenden. Diese Methode verwendet ein Array-ähnliches oder iterierbares Objekt (einschließlich NodeList, HTML-Sammlungen, Zeichenfolgen usw.) und gibt eine neue Array-Instanz zurück. Sie können es so verwenden:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

Da die Array.from()Methode shimmbar ist, können Sie sie in es5-Code wie diesem verwenden

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

Weitere Informationen finden Sie auf der MDN- Seite.

Um zu überprüfen , Unterstützung aktuelle Browser .

ODER

Eine andere Möglichkeit für es2015 ist die Verwendung des Spread-Operators.

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

MDN-Spread-Operator

Spread Operator - Browser-Unterstützung

Mischel Tanvir Habib
quelle
2

Meine Lösung:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Bakos Bence
quelle
1
Es ist oft keine gute Idee, die Funktionalität von DOM durch Prototypen zu erweitern, insbesondere in älteren Versionen von IE ( Artikel ).
KFE
0

NodeList ist Teil der DOM-API. Schauen Sie sich die ECMAScript-Bindungen an, die auch für JavaScript gelten. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . Die Funktion nodeList und eine schreibgeschützte Längeneigenschaft sowie die Funktion item (index) geben einen Knoten zurück.

Die Antwort ist, Sie müssen iterieren. Es gibt keine Alternative. Foreach wird nicht funktionieren. Ich arbeite mit Java DOM API-Bindungen und habe das gleiche Problem.

RandominstanceOfLivingThing
quelle
Aber gibt es einen bestimmten Grund, warum es nicht implementiert werden sollte? Sowohl jQuery als auch Dojo haben es in ihren eigenen Bibliotheken implementiert
Snakes and Coffee
2
aber wie ist es ein designkonflikt?
Schlangen und Kaffee
0

Überprüfen Sie MDN auf NodeList.forEach- Spezifikation.

NodeList.forEach(function(item, index, nodeList) {
    // code block here
});

Verwenden Sie im IE die Antwort von akuhn :

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
VesperX
quelle