Come convertire un elenco di nodi DOM in un array in Javascript?


96

Ho una funzione Javascript che accetta un elenco di nodi HTML, ma si aspetta un array Javascript (esegue alcuni metodi Array su quello) e voglio alimentarlo l'output di Document.getElementsByTagNameche restituisce un elenco di nodi DOM.

Inizialmente ho pensato di utilizzare qualcosa di semplice come:

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

E questo funziona bene in tutti i browser, tranne ovviamente Internet Explorer che restituisce l'errore "Oggetto JScript previsto", poiché apparentemente l'elenco dei nodi DOM restituito dai Document.getElement*metodi non è un oggetto JScript sufficiente per essere l'obiettivo di una chiamata di funzione.

Avvertenze: non mi dispiace scrivere codice specifico per Internet Explorer, ma non sono autorizzato a utilizzare alcuna libreria Javascript come JQuery perché sto scrivendo un widget da incorporare in un sito Web di terze parti e non posso caricare librerie esterne che creerà conflitto per i client.

Il mio ultimo sforzo disperato è quello di iterare l'elenco dei nodi DOM e creare un array da solo, ma c'è un modo migliore per farlo?


Meglio ancora, crea una funzione per convertire dall'elenco dei nodi DOM, ma questa sarebbe davvero la mia soluzione, penso che tu abbia capito bene.
Kristoffer Sall-Storgaard

> for (i = 0; i & lt; x.length; i ++) Perché ottenere la lunghezza di NodeList ad ogni iterazione? Non è solo una perdita di tempo, ma poiché le NodeList sono raccolte in tempo reale, se qualcosa nel corpo del ciclo cambia la sua lunghezza, potresti eseguire il ciclo all'infinito o colpire un indice fuori limite. Quest'ultimo è il peggio che può accadere se si assegna la lunghezza a una variabile e un errore è molto meglio di un ciclo infinito.

Questa è una domanda davvero vecchia, ma jQuery è stato creato con il metodo .noConflict specificamente in modo da non causare conflitti con altre librerie (anche se stesso), il che significa che più versioni di jQuery potrebbero essere caricate su una pagina. Detto questo, è meglio evitare di utilizzare / caricare una libreria a meno che non sia assolutamente necessario.
vol7ron

@ vol7ron: vai avanti velocemente al 2016 e tutti sono ancora tesi riguardo alle dimensioni che le librerie javascript aggiungono alla pagina. Certo, JQuery minimizzato e gzippato è di 30KB, sono ancora 30KB di troppo solo per trasformare un elenco di nodi :-)
Guss

Risposte:


64

Le NodeList sono oggetti host , l'utilizzo del Array.prototype.slicemetodo sugli oggetti host non è garantito per funzionare, la specifica ECMAScript afferma:

Se la funzione slice può essere applicata correttamente a un oggetto host dipende dall'implementazione.

Ti consiglierei di creare una semplice funzione per iterare NodeListe aggiungere ogni elemento esistente a un array:

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

AGGIORNARE:

Come suggeriscono altre risposte, ora puoi utilizzare negli ambienti moderni la sintassi diffusa o il Array.frommetodo:

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

Ma a pensarci bene, immagino che il caso d'uso più comune per convertire un NodeList in un Array sia iterare su di esso, e ora l' NodeList.prototypeoggetto ha il forEachmetodo in modo nativo , quindi se ti trovi in ​​un ambiente moderno puoi usarlo direttamente, o avere un pollyfill.


2
Questo sta creando un array con l'ordine originale della lista invertito, che non suppongo sia ciò che vuole l'OP. Volevi fare array[i] = obj[i]invece di array.push(obj[i])?
Tim Down

@ Tim, giusto, l'avevo già fatto prima ma l'ho modificato ieri sera senza accorgermene (3:00 ora locale :), Grazie !.
CMS

9
In quali circostanze sarebbe obj.lengthqualcosa di diverso da un valore intero?
Peter

1
Non posso credere che sia così complicato. Brutta. Questa è un'esigenza molto comune nella programmazione Web / JS. Un nuovo metodo per la prossima versione del linguaggio?
Andrew Koper

1
@AlbertoPerez, sei il benvenuto !. Saludos hasta Madrid!
CMS

126

In es6 puoi semplicemente usare come segue:

  • Operatore di diffusione

     var elements = [... nodelist]
  • Utilizzando Array.from

     var elements = Array.from(nodelist)

ulteriori riferimenti su https://developer.mozilla.org/en-US/docs/Web/API/NodeList


4
così facile con Array.from(): D
Josan Iracheta

4
nel caso qualcuno stia usando questo approccio con Typescript (a ES5), Array.fromfunziona solo , poiché TS lo trasferisce a nodelist.slice- che non è supportato.
Peter Albert

Ho risposto lo stesso un anno prima e tu mi hai passato alle votazioni? Non posso spiegare questo ..
vsync

3
@vsync, la tua risposta non menzionaArray.from
ESR

@EdmundReed - quindi? come lo giustifica. è più lungo per scrivere quindi nella situazione reale non verrà mai utilizzato, spreadviene utilizzato solo .
vsync

16

Utilizzando spread (ES2015) , è facile come:[...document.querySelectorAll('p')]

(opzionale: usa Babel per trasferire il codice ES6 sopra alla sintassi ES5)


Provalo nella console del tuo browser e guarda la magia:

for( links of [...document.links] )
  console.log(links);

Almeno all'ultimo chrome, 44, ottengo questo: Uncaught TypeError: document.querySelectorAll non è una funzione (...)
Nick

@OmidHezaveh - Come ho detto, questo è il codice ES6. Non so se Chrome 44 supporta ES6 e, in tal caso, a quale copertura. È un browser vecchio di quasi un anno e ovviamente dovresti eseguire questo codice su un browser che supporta la diffusione ES6.
vsync

Oppure trasferiscilo in es5 prima dell'esecuzione
HelloWorld

8

Usa questo semplice trucco

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})

Puoi spiegare per favore perché pensi che questo abbia maggiori possibilità di successo rispetto all'uso Array.prototype.slice(o [].slicecome lo dici tu)? Come nota, vorrei commentare che l'errore specifico di IE che ho documentato in Q si verifica in IE 8 o inferiore, dove mapnon è comunque implementato. In IE 9 ("modalità standard") o superiore, entrambi slicee mapriescono allo stesso modo.
Guss

6

Sebbene non sia proprio uno shim appropriato, poiché non ci sono specifiche che richiedono di lavorare con elementi DOM, ne ho creato uno per consentirti di usarlo slice()in questo modo: https://gist.github.com/brettz9/6093105

AGGIORNAMENTO : Quando ho sollevato questo problema con l'editor della specifica DOM4 (chiedendo se potevano aggiungere le proprie restrizioni agli oggetti host (in modo che la specifica richiedesse agli implementatori di convertire correttamente questi oggetti se usati con metodi array) oltre la specifica ECMAScript che aveva consentito l'indipendenza dall'implementazione), ha risposto che "Gli oggetti host sono più o meno obsoleti per ES6 / IDL." Vedo per http://www.w3.org/TR/WebIDL/#es-array che le specifiche possono utilizzare questo IDL per definire "oggetti array di piattaforme" ma http://www.w3.org/TR/domcore/ non sembra che stia usando il nuovo IDL per HTMLCollection(anche se sembra che lo stia facendo perché Element.attributesafferma solo esplicitamente che sta usando WebIDL per DOMString e DOMTimeStamp). Lo vedo[ArrayClass](che eredita da Array.prototype) viene utilizzato per NodeList(ed NamedNodeMapè ora deprecato a favore dell'unico elemento che lo utilizzerebbe ancora Element.attributes). In ogni caso, sembra che stia per diventare uno standard. L'ES6 Array.frompotrebbe anche essere più conveniente per tali conversioni che dover specificare Array.prototype.slicee semanticamente più chiaro di [].slice()(e la forma più breve, Array.slice()(un "array generico"), per quanto ne so, non è diventata un comportamento standard).


Ho aggiornato per indicare che le specifiche potrebbero andare nella direzione di richiedere questo comportamento.
Brett Zamir

5

Oggi, nel 2018, potremmo usare ECMAScript 2015 (6a edizione) o ES6, ma non tutti i browser possono capirlo (ad es. IE non lo capisce tutto). Se vuoi puoi usare ES6 come segue: var array = [... NodeList];( come operatore di spread ) o var array = Array.from(NodeList);.

In altri casi (se non puoi usare ES6) puoi usare il modo più breve per convertire un NodeListin un Array:

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

Per esempio:

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">

Ma se si desidera iterare DOMsolo facilmente l'elenco dei nodi, non è necessario convertire un file NodeLista in un file Array. È possibile eseguire il ciclo degli elementi in un NodeListutilizzo:

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">

Non essere tentato di usare for...ino for each...inenumerare gli elementi nell'elenco, poiché ciò enumererà anche la lunghezza e le proprietà dell'elemento NodeListe causerà errori se il tuo script presume che abbia a che fare solo con oggetti elemento. Inoltre, for..innon è garantito visitare le proprietà in un ordine particolare. for...ofi cicli verranno eseguiti correttamente sugli oggetti NodeList.

Vedi anche:


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

Questo dovrebbe funzionare, attraversare il browser e ottenere tutti i nodi "elemento".


1
Questo è fondamentalmente lo stesso della risposta di @ CMS, tranne per il fatto che presume che io voglia solo nodi elemento, cosa che non voglio.
Guss
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.