Perché nodelist non ha forEach?


91

Stavo lavorando a un breve script per modificare il <abbr>testo interno degli elementi, ma nodelistho scoperto che non ha un forEachmetodo. So che nodelistnon eredita da Array, ma non sembra che forEachsarebbe un metodo utile da avere? C'è un problema di implementazione particolare di cui non sono a conoscenza che impedisce l'aggiunta forEacha nodelist?

Nota: sono consapevole che Dojo e jQuery hanno entrambi forEachin qualche forma per i loro nodelist. Non posso usare nessuno dei due a causa delle limitazioni.


6
Ciao dal futuro! nodeList ha forEach da ES6 .
Blaise

Risposte:


94

NodeList ora ha forEach () in tutti i principali browser

Vedi nodeList forEach () su MDN .

Risposta originale

Nessuna di queste risposte spiega perché NodeList non eredita da Array, permettendogli così di avere forEache tutto il resto.

La risposta si trova in questo thread di es-discuss . In breve, rompe il web:

Il problema era il codice che presumeva erroneamente che instanceof significasse che l'istanza era un array in combinazione con Array.prototype.concat.

C'era un bug nella libreria di chiusura di Google che causava il fallimento di quasi tutte le app di Google a causa di ciò. La libreria è stata aggiornata non appena è stata trovata, ma potrebbe esserci ancora del codice là fuori che fa lo stesso presupposto errato in combinazione con concat.

Cioè, un codice ha fatto qualcosa di simile

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

Tuttavia, concattratterà gli array "reali" (non l'istanza di Array) in modo diverso dagli altri oggetti:

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

quindi questo significa che il codice sopra si è rotto quando xera un NodeList, perché prima è andato lungo il doSomethingElseWith(x)percorso, mentre in seguito è andato lungo il otherArray.concat(x)percorso, il che ha fatto qualcosa di strano poiché xnon era un vero array.

Da tempo è stata proposta una Elementsclasse che fosse una vera e propria sottoclasse di Array, e che sarebbe stata usata come "la nuova NodeList". Tuttavia, questo è stato rimosso dallo standard DOM , almeno per ora, poiché non era ancora possibile implementarlo per una serie di motivi tecnici e relativi alle specifiche.


11
Mi sembra una cattiva chiamata. Seriamente, penso che sia la decisione giusta per rompere le cose di tanto in tanto, soprattutto se significa che abbiamo API sane per il futuro. Inoltre, non è che il Web sia nemmeno vicino ad essere una piattaforma stabile, le persone sono abituate al loro javascript di 2 anni che non funziona più come previsto ..
JoyalToTheWorld

3
"Breaks the web"! = "Breaks Google stuff"
Matt

Perché non prendere in prestito il metodo forEach da Array.prototype? Ad esempio, invece di aggiungere Array come prototipo, basta fare this.forEach = Array.prototype.forEach nel costruttore, o anche solo implementare forEach in modo univoco per NodeList?
PopKernel

1
Nota; questo aggiornamento NodeList now has forEach() in all major browserssembra implicare che IE non è un browser importante. Si spera che sia vero per alcune persone, ma non è vero per me (ancora).
Graham P Heath,

58

Tu puoi fare

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

    // Your code here.

} );

3
Array.prototype.forEach.callpuò essere abbreviato a[].forEach.call
CodeBrauer

7
@CodeBrauer: non si tratta solo di accorciare Array.prototype.forEach.call, ma di creare un array vuoto e di utilizzare il suo forEachmetodo.
Paul D. Waite,

34

Puoi considerare la creazione di un nuovo array di nodi.

  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.  

  });

Nota: questo è solo un elenco / array di riferimenti ai nodi che stiamo creando qui, nessun nodo duplicato.

  nodes[0] === nodeList[0] // will be true

22
O semplicemente fallo Array.prototype.forEach.call(nodeList, fun).
akuhn

Vorrei anche suggerire aliasing la funzione forEach tale che: var forEach = Array.prototype.forEach.call(nodeList, callback);. Ora puoi semplicemente chiamareforEach(nodeList, callback);
Andrew Craswell

19

Mai dire mai, è il 2016 e l' NodeListoggetto ha implementato un forEachmetodo nell'ultimo chrome (v52.0.2743.116).

È troppo presto per usarlo in produzione poiché altri browser non lo supportano ancora (FF 49 testato) ma immagino che presto sarà standardizzato.


2
Anche Opera lo supporta e verrà aggiunto il supporto nella v50 di Firefox, previsto per il rilascio il 15/11/16.
Shaggy

1
Sebbene implementato, non fa parte di nessuno standard. È comunque meglio fare ciò Array.prototype.slice.call(nodelist).forEach(…)che è standard e funziona con i vecchi browser.
Nate

17

In breve, è un conflitto di progettazione implementare quel metodo.

Da MDN:

Perché non posso usare forEach o mappare su un NodeList?

I NodeList sono usati in modo molto simile agli array e sarebbe allettante usare i metodi Array.prototype su di essi. Tuttavia, questo è impossibile.

JavaScript ha un meccanismo di ereditarietà basato su prototipi. Le istanze di array ereditano i metodi di array (come forEach o map) perché la loro catena di prototipi è simile alla seguente:

myArray --> Array.prototype --> Object.prototype --> null (la catena del prototipo di un oggetto può essere ottenuta chiamando più volte Object.getPrototypeOf)

forEach, map e like sono proprietà proprie dell'oggetto Array.prototype.

A differenza degli array, la catena di prototipi NodeList ha il seguente aspetto:

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

NodeList.prototype contiene il metodo item, ma nessuno dei metodi Array.prototype, quindi non possono essere utilizzati su NodeLists.

Fonte: https://developer.mozilla.org/en-US/docs/DOM/NodeList (scorri verso il basso fino a Why can't I use forEach or map on a NodeList? )


8
Quindi, dato che è un elenco, perché è progettato in questo modo? Cosa li impedisce di fare catena myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null:?
Igor Pantović

14

Se desideri utilizzare forEach su NodeList, copia semplicemente quella funzione da Array:

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

Questo è tutto, ora puoi usarlo nello stesso modo in cui faresti per Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});

4
Tranne che la modifica delle classi di base non è molto esplicita per il lettore medio. In altre parole, nel profondo del codice devi ricordare ogni personalizzazione che fai agli oggetti di base del browser. Affidarsi alla documentazione MDN non è più utile perché gli oggetti hanno cambiato comportamento rispetto alla norma. È meglio applicare esplicitamente il prototipo al momento della chiamata in modo che il lettore possa facilmente rendersi conto che forEach è un'idea presa in prestito e non qualcosa che fa parte della definizione del linguaggio. Vedi la risposta che @akuhn ha sopra.
Sukima

@Sukima fuorviato da premesse sbagliate. In questo caso specifico, l'approccio dato corregge il comportamento di NodeList come previsto dallo sviluppatore. Questo è il modo più corretto per risolvere il problema della classe di sistema. (NodeList sembra non essere finito e dovrebbe essere corretto nelle future versioni del linguaggio.)
Daniel Garmoshka,

1
ora che NodeList.forEach esiste, questo diventa un divertentissimo polyfill!
Damon

7

In ES2015, ora puoi utilizzare il forEachmetodo per nodeList.

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

Vedi The MDN Link

Tuttavia, se si desidera utilizzare raccolte HTML o altri oggetti simili ad array, in es2015, è possibile utilizzare Array.from()method. Questo metodo accetta un oggetto simile a un array o iterabile (inclusi nodeList, raccolte HTML, stringhe, ecc.) E restituisce una nuova istanza di Array. Puoi usarlo in questo modo:

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

Poiché il Array.from()metodo è scintillante, puoi usarlo nel codice es5 come questo

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

Per i dettagli, vedere la pagina MDN .

Per verificare il supporto del browser corrente .

O

un altro modo di es2015 è usare l'operatore spread.

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

Operatore di diffusione MDN

Operatore di diffusione - Supporto browser


2

La mia soluzione:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;

1
Spesso non è una buona idea estendere la funzionalità di DOM tramite prototipi, specialmente nelle versioni precedenti di IE ( articolo ).
KFE

0

NodeList fa parte dell'API DOM. Guarda le associazioni ECMAScript che si applicano anche a JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . Il nodeList e una proprietà di lunghezza di sola lettura e una funzione item (index) per restituire un nodo.

La risposta è che devi iterare. Non ci sono alternative. Foreach non funzionerà. Lavoro con le associazioni API DOM Java e ho lo stesso problema.


Ma c'è un motivo particolare per cui non dovrebbe essere implementato? Sia jQuery che Dojo l'hanno implementato nelle proprie librerie
Snakes and Coffee,

2
ma come è un conflitto di design?
Serpenti e caffè

0

Controlla MDN per NodeList.forEach specifica.

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

In IE, usa la risposta di akuhn :

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
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.