Warum gibt document.querySelectorAll eine StaticNodeList anstelle eines echten Arrays zurück?

103

Es nervt mich, dass ich nicht document.querySelectorAll(...).map(...)einmal in Firefox 3.6 etwas tun kann und immer noch keine Antwort finde. Deshalb dachte ich, ich würde die Frage aus diesem Blog auf SO posten:

http://blowery.org/2008/08/29/yay-for-queryselectorall-boo-for-staticnodelist/

Kennt jemand einen technischen Grund, warum Sie kein Array erhalten? Oder warum ein StaticNodeList nicht aus einem Array in einer solchen Art und Weise erben , dass Sie nutzen könnten map, concatetc?

(Übrigens, wenn es nur eine Funktion ist, die Sie möchten, können Sie so etwas wie NodeList.prototype.map = Array.prototype.map;... aber warum ist diese Funktionalität (absichtlich?) Überhaupt blockiert?)

Kev
quelle
3
Tatsächlich gibt getElementsByTagName auch kein Array zurück, sondern eine Sammlung. Wenn Sie es wie ein Array verwenden möchten (mit Methoden wie concat usw.), müssen Sie eine solche Sammlung in ein Array konvertieren, indem Sie eine Schleife ausführen und jedes Element des kopieren Sammlung in ein Array. Niemand hat sich jemals darüber beschwert.
Marco Demaio

Antworten:

81

Ich glaube, es ist eine philosophische Entscheidung des W3C. Das Design des W3C DOM [spec] ist ziemlich senkrecht zur Gestaltung von JavaScript, wie die DOM ist gemeint Plattform und sprachneutral sein.

Entscheidungen wie " getElementsByFoo()gibt eine Bestellung zurück NodeList" oder " querySelectorAll()gibt eine zurück StaticNodeList" sind sehr beabsichtigt, sodass Implementierungen sich nicht darum kümmern müssen, ihre zurückgegebene Datenstruktur basierend auf sprachabhängigen Implementierungen auszurichten (wie .mapsie in Arrays in JavaScript und Ruby verfügbar sind) nicht auf Listen in C #).

Das W3C Ziel niedrig: sie werden sagen , ein NodeListeine enthalten sollten nur lesbar .lengthEigenschaft vom Typ unsigned long , weil sie glauben , dass jede Implementierung zumindest Unterstützung , dass , aber sie werden nicht sagen ausdrücklich , dass der []soll Indexoperator immer Positionselemente zur Unterstützung überlastet werden, weil sie keine arme kleine Sprache unterdrücken wollen, die implementiert werden will, getElementsByFoo()aber das Überladen von Operatoren nicht unterstützen kann. Es ist eine weit verbreitete Philosophie, die in weiten Teilen der Spezifikation vorhanden ist.

John Resig hat eine ähnliche Option wie Ihre geäußert , zu der er hinzufügt :

Mein Argument ist nicht so sehr, dass NodeIteratores nicht sehr DOM-ähnlich ist, sondern dass es nicht sehr JavaScript-ähnlich ist. Die in der JavaScript-Sprache vorhandenen Funktionen werden nicht optimal genutzt und optimal genutzt ...

Ich kann mich ein wenig einfühlen. Wenn das DOM speziell für JavaScript-Funktionen geschrieben wurde, wäre es viel weniger umständlich und intuitiver zu bedienen. Gleichzeitig verstehe ich die Designentscheidungen des W3C.

Halbmond frisch
quelle
Danke, das hilft mir, die Situation zu verstehen.
Kev
@Kev: Ich habe Ihren Kommentar auf dieser Blog-Artikelseite gesehen, in dem gefragt wurde, wie Sie das StaticNodeListin ein Array konvertieren würden . Ich würde die Antwort von @ mck89 als den Weg zur Konvertierung eines NodeList/ StaticNodeListin ein natives Array unterstützen, aber das wird im IE (8 obv) mit einem JScript-Fehler fehlschlagen, da diese Objekte gehostet werden / "special".
Crescent Fresh
Das stimmt, deshalb habe ich ihn positiv bewertet. Jemand anderes hat meine +1 abgesagt. Was meinst du mit gehostet / speziell?
Kev
1
@Kev: Gehostete Variablen sind alle Variablen, die von der "Host" -Umgebung bereitgestellt werden (z. B. ein Webbrowser). Zum Beispiel document, windowIE, usw. oft implementiert dieser „speziell“ (zum Beispiel als COM - Objekte) , die manchmal nicht Übereinstimmen normaler Verwendung in kleiner und subtilen Art und Weise, wie die Array.prototype.slice.callBombardierung als gegeben ein StaticNodeList;)
Crescent Frisch
199

Sie können den Spread-Operator ES2015 (ES6) verwenden :

[...document.querySelectorAll('div')]

konvertiert StaticNodeList in Array von Elementen.

Hier ist ein Beispiel für die Verwendung.

[...document.querySelectorAll('div')].map(x => console.log(x.innerHTML))
<div>Text 1</div>
<div>Text 2</div>

Vlad Bezden
quelle
24
Eine andere Möglichkeit ist die Verwendung von Array.from () :Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Michael Berdyshev
42

Ich weiß nicht, warum es eine Knotenliste anstelle eines Arrays zurückgibt, vielleicht weil es wie getElementsByTagName das Ergebnis aktualisiert, wenn Sie das DOM aktualisieren. Eine sehr einfache Methode, um dieses Ergebnis in ein einfaches Array umzuwandeln, ist:

Array.prototype.slice.call(document.querySelectorAll(...));

und dann können Sie tun:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);
mck89
quelle
3
Tatsächlich wird das Ergebnis beim Aktualisieren des DOM nicht aktualisiert - daher "statisch". Sie müssen qSA erneut manuell aufrufen, um das Ergebnis zu aktualisieren. +1 für die sliceLinie.
Kev
1
Ja, wie Kev sagte: qSA-Ergebnismenge ist statisch, getElementsByTagName () -Ergebnismenge ist dynamisch.
joonas.fi
IE8 unterstützt querySelectorAll () nur im Standardmodus
mbokil
13

Nur um zu dem hinzuzufügen, was Crescent gesagt hat:

Wenn es sich nur um eine gewünschte Funktion handelt, können Sie beispielsweise NodeList.prototype.map = Array.prototype.map ausführen

Tu das nicht! Es ist überhaupt nicht garantiert zu funktionieren.

Kein JavaScript- oder DOM / BOM-Standard gibt an, dass die NodeListKonstruktorfunktion sogar als globale / windowEigenschaft existiert oder dass die NodeListzurückgegebene Funktion von querySelectorAllihr erbt oder dass ihr Prototyp beschreibbar ist oder dass die Funktion Array.prototype.maptatsächlich auf einer NodeList funktioniert.

Eine NodeList darf ein 'Host-Objekt' sein (und ist eines im IE und einigen älteren Browsern). Die ArrayMethoden sind so definiert, dass sie mit jedem nativen JavaScript-Objekt ausgeführt werden dürfen, das Zahlen und lengthEigenschaften verfügbar macht. Sie müssen jedoch nicht mit Hostobjekten arbeiten (und im IE nicht).

Es ist ärgerlich, dass Sie nicht alle Array-Methoden in DOM-Listen erhalten (alle, nicht nur StaticNodeList), aber es gibt keinen zuverlässigen Weg, dies zu umgehen. Sie müssen jede DOM-Liste, die Sie erhalten, manuell in ein Array konvertieren:

Array.fromList= function(list) {
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
};

Array.fromList(element.childNodes).forEach(function() {
    ...
});
Bobince
quelle
1
Schieß, daran habe ich nicht gedacht. Vielen Dank!
Kev
Ich stimme +1 zu. Nur ein Kommentar, ich denke, wenn Sie "var array = []" anstelle von "var array = new Array (list.length)" machen, wird der Code noch kürzer. Aber ich bin interessiert, wenn Sie wissen, dass dies ein Problem sein könnte.
Marco Demaio
@ MarcoDemaio: Nein, kein Problem. new Array(n)gibt dem JS-Terp nur einen Hinweis darauf, wie lange das Array enden wird. Dies könnte es ihm ermöglichen, diese Menge an Speicherplatz im Voraus zuzuweisen, was möglicherweise zu einer Beschleunigung führen würde, da einige Speicherumverteilungen vermieden werden könnten, wenn das Array wächst. Ich weiß nicht, ob es in modernen Browsern tatsächlich hilft ... Ich würde nicht messbar vermuten.
Bobince
2
Jetzt ist es in Array.from ()
Michael Berdyshev
2

Ich denke, Sie können einfach folgen

Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

Es funktioniert perfekt für mich

Max Leps
quelle
0

Dies ist eine Option, die ich zu den anderen Möglichkeiten hinzufügen möchte, die andere hier vorschlagen. Es ist nur für intellektuellen Spaß gedacht und wird nicht empfohlen .


Nur zum Spaß , hier ist eine Möglichkeit, "zu zwingen", sich querySelectorAllzu knien und sich vor dir zu verbeugen:

Element.prototype.querySelectorAll = (function(QSA){
    return function(){
        return [...QSA.call(this, arguments[0])]
    }
})(Element.prototype.querySelectorAll);

Jetzt fühlt es sich gut an, diese Funktion zu durchlaufen und zu zeigen, wer der Boss ist. Jetzt weiß ich nicht, was besser ist, wenn ich einen ganz neuen Wrapper für benannte Funktionen erstelle und dann Ihren gesamten Code diesen seltsamen Namen verwenden lasse (so ziemlich im jQuery-Stil) oder die Funktion wie oben einmal überschreibe, damit der Rest Ihres Codes noch in der Lage ist um den ursprünglichen DOM-Methodennamen zu verwenden querySelectorAll.

Ich würde dies in keiner Weise empfehlen, es sei denn, Sie geben ehrlich gesagt kein [Sie wissen was].

vsync
quelle