Perché document.querySelectorAll restituisce uno StaticNodeList invece di un vero array?


103

Mi infastidisce il fatto che non riesco a farlo document.querySelectorAll(...).map(...)anche in Firefox 3.6, e non riesco ancora a trovare una risposta, quindi ho pensato di pubblicare su SO la domanda da questo blog:

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

Qualcuno conosce un motivo tecnico per cui non si ottiene un array? O perché uno StaticNodeList non eredita da una matrice in tal modo un che si potrebbe usare map, concatecc?

(A proposito, se è solo una funzione che desideri, puoi fare qualcosa come NodeList.prototype.map = Array.prototype.map;... ma ancora, perché questa funzionalità è (intenzionalmente?) Bloccata in primo luogo?)


3
In realtà anche getElementsByTagName non restituisce un Array, ma una raccolta, e se vuoi usarlo come un Array (con metodi come concat ecc.) Devi convertire tale raccolta in un Array facendo un ciclo e copiando ogni elemento del raccolta in un array. Nessuno si è mai lamentato di questo.
Marco Demaio

Risposte:


81

Credo che sia una decisione filosofica del W3C. Il design del W3C DOM [spec] è abbastanza ortogonale al design di JavaScript, poiché il DOM è pensato per essere indipendente dalla piattaforma e dal linguaggio.

Decisioni come " getElementsByFoo()restituisce un ordinato NodeList" o " querySelectorAll()restituisce un StaticNodeList" sono molto intenzionali, quindi le implementazioni non devono preoccuparsi di allineare la struttura dei dati restituiti in base alle implementazioni dipendenti dal linguaggio (come .mapessere disponibili su Arrays in JavaScript e Ruby, ma non negli elenchi in C #).

La bassa W3C obiettivo: Diranno una NodeListdeve contenere una sola lettura .lengthproprietà di tipo unsigned long perché credono ogni implementazione può almeno sostenere che , ma non dirà esplicitamente che l' []operatore di indice dovrebbe essere sovraccaricato per sostenere ottenere elementi posizionali, perché non vogliono ostacolare qualche povero linguaggio che arriva che vuole implementare getElementsByFoo()ma non può supportare il sovraccarico dell'operatore. È una filosofia prevalente presente in gran parte delle specifiche.

John Resig ha espresso un'opzione simile alla tua, a cui aggiunge :

La mia tesi non è tanto che NodeIteratornon sia molto simile a DOM, è che non sia molto simile a JavaScript. Non sfrutta le funzionalità presenti nel linguaggio JavaScript e le utilizza al meglio delle sue capacità ...

Mi immedesimo in qualche modo. Se il DOM fosse scritto specificatamente con in mente le funzionalità JavaScript, sarebbe molto meno scomodo e più intuitivo da usare. Allo stesso tempo capisco le decisioni di progettazione del W3C.


Grazie, questo mi aiuta a dare un senso alla situazione.
Kev

@Kev: ho visto il tuo commento sulla pagina dell'articolo del blog in cui si chiedeva come convertirti StaticNodeListin un array. Vorrei approvare la risposta di @ mck89 come la strada da percorrere per convertire un NodeList/ StaticNodeListin un array nativo, ma ciò fallirà in IE (8 obv) con un errore JScript, poiché quegli oggetti sono ospitati / "speciali".
Crescent Fresh

È vero, è per questo che l'ho votato positivamente. Qualcun altro ha però cancellato il mio +1. Cosa intendi per ospitato / speciale?
Kev

1
@Kev: le variabili ospitate sono tutte le variabili fornite dall'ambiente "host" (ad esempio un browser web). Ad esempio document, windowecc. IE spesso li implementa "appositamente" (ad esempio come oggetti COM) che a volte non sono conformi all'uso normale, in modi piccoli e sottili, come i Array.prototype.slice.callbombardamenti quando viene dato un StaticNodeList;)
Crescent Fresh

200

È possibile utilizzare l' operatore di diffusione ES2015 (ES6) :

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

convertirà StaticNodeList in un array di elementi.

Ecco un esempio su come usarlo.

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


24
Un altro modo è usare Array.from () :Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Michael Berdyshev

42

Non so perché restituisce un elenco di nodi invece di un array, forse perché come getElementsByTagName aggiornerà il risultato quando aggiorni il DOM. Ad ogni modo un metodo molto semplice per trasformare quel risultato in un semplice array è:

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

e poi puoi fare:

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

3
In realtà non aggiorna il risultato quando aggiorni il DOM, quindi "statico". Devi chiamare di nuovo manualmente qSA per aggiornare il risultato. +1 per la slicelinea però.
Kev

1
Sì, come ha detto Kev: il set di risultati qSA è statico, il set di risultati getElementsByTagName () è dinamico.
joonas.fi

IE8 supporta solo querySelectorAll () in modalità standard
mbokil

13

Solo per aggiungere a ciò che ha detto Crescent,

se è solo una funzione che vuoi, puoi fare qualcosa come NodeList.prototype.map = Array.prototype.map

Non farlo! Non è affatto garantito che funzioni.

Nessuno standard JavaScript o DOM / BOM specifica che la NodeListfunzione-costruttore esiste anche come windowproprietà / globale , o che il NodeListrestituito da querySelectorAllerediterà da esso, o che il suo prototipo è scrivibile, o che la funzione funzionerà Array.prototype.mapeffettivamente su un NodeList.

Un NodeList può essere un "oggetto host" (ed è uno, in IE e in alcuni browser meno recenti). I Arraymetodi sono definiti come autorizzati a operare su qualsiasi "oggetto nativo" JavaScript che espone lengthproprietà numeriche e , ma non sono tenuti a lavorare su oggetti host (e in IE non lo fanno).

È fastidioso che non si ottengano tutti i metodi degli array negli elenchi DOM (tutti, non solo StaticNodeList), ma non esiste un modo affidabile per aggirarlo. Dovrai convertire manualmente ogni elenco DOM che torni in un array:

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() {
    ...
});

1
Spara, non ci ho pensato. Grazie!
Kev

Accetto +1. Solo un commento, penso che fare "var array = []" invece di "var array = new Array (list.length)" renderà il codice ancora più breve. Ma mi interessa se sai che potrebbe esserci un problema nel farlo.
Marco Demaio

@MarcoDemaio: No, nessun problema. new Array(n)dà solo un suggerimento al terp JS su quanto tempo finirà l'array. Ciò potrebbe consentirgli di allocare quella quantità di spazio in anticipo, il che potrebbe potenzialmente tradursi in una accelerazione poiché alcune riallocazioni della memoria potrebbero essere evitate man mano che l'array cresce. Non so se in realtà aiuta nei browser moderni, però ... sospetto non sia misurabile.
bobince

2
Ora è implementato in Array.from ()
Michael Berdyshev

2

Penso che tu possa semplicemente seguire

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

Funziona perfettamente per me


0

Questa è un'opzione che volevo aggiungere alla gamma di altre possibilità suggerite da altri qui. È pensato solo per divertimento intellettuale e non è consigliato .


Solo per il gusto di farlo, ecco un modo per "forzare" querySelectorAlla inginocchiarsi e inchinarsi a te:

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

Ora è bello scavalcare quella funzione, mostrandogli chi è il capo. Ora non so cosa c'è di meglio, creando un wrapper di funzione con nome completamente nuovo e poi fai in modo che tutto il tuo codice usi quel nome strano (praticamente in stile jQuery) o sovrascriva la funzione come sopra una volta in modo che il resto del tuo codice sarebbe ancora in grado per utilizzare il nome del metodo DOM originale querySelectorAll.

  • Tale approccio eliminerebbe il possibile utilizzo di sotto-metodi

Non lo consiglierei in alcun modo, a meno che onestamente non te ne dia un [sai cosa].

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.