JS: iterando il risultato di getElementsByClassName usando Array.forEach


240

Voglio iterare su alcuni elementi DOM, sto facendo questo:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

ma ricevo un errore:

document.getElementsByClassName ("myclass"). forEach non è una funzione

Sto usando Firefox 3, quindi so che entrambi getElementsByClassNamee Array.forEachsono presenti. Funziona bene:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

È il risultato di getElementsByClassNameuna matrice? Se no, cos'è?

Risposte:


384

No. Come specificato in DOM4 , è un HTMLCollection(nei browser moderni, almeno. I browser precedenti hanno restituito un NodeList).

In tutti i browser moderni (praticamente qualsiasi altra IE <= 8), puoi chiamare il forEachmetodo Array , passandogli l'elenco degli elementi (sia esso HTMLCollectiono NodeList) come thisvalore:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

Se sei nella felice posizione di poter usare ES6 (cioè puoi tranquillamente ignorare Internet Explorer o stai usando un transpiler ES5), puoi usare Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});

29
Non è necessario prima convertirlo in un array. Basta usare [].forEach.call(elsArray, function () {...}).
kay - SE è malvagio il

1
NON è un NodeList. È un oggetto simile a un array. Non penso nemmeno che abbia un tipo di istanza. querySelectorAllil metodo restituisce tuttavia un NodeList.
Maksim Vi.

2
@MaksimVi. Hai assolutamente ragione: DOM4 specifica che document.getElementsByClassName()dovrebbe restituire un HTMLCollection(che è molto simile ma non un NodeList). Grazie per aver segnalato l'errore.
Tim Down,

@MaksimVi .: Mi chiedo se sia cambiato ad un certo punto. Di solito controllo queste cose.
Tim Down,

1
@TimDown, grazie per la segnalazione HTMLCollection. Ora finalmente posso usare HTMLCollection.prototype.forEach = Array.prototype.forEach;nel mio codice.
Maksim Vi.

70

È possibile utilizzare Array.fromper convertire la raccolta in array, che è molto più pulito di Array.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

Nei browser più vecchi che non supportano Array.from, devi usare qualcosa come Babel.


ES6 aggiunge anche questa sintassi:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

Resta distruttivo con le ...opere su tutti gli oggetti simili a array, non solo gli array stessi, ma una buona vecchia sintassi dell'array viene utilizzata per costruire un array dai valori.


Mentre la funzione alternativa querySelectorAll(che in qualche modo rende getElementsByClassNameobsoleta) restituisce una raccolta che ha forEachnativamente, altri metodi come mapo filtermancano, quindi questa sintassi è ancora utile:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);

6
Nota: senza traspillare come suggerito (Babele), questo NON è compatibile in IE <Edge, Opera, Safari <9, browser Android, Chrome per Android, ... ecc.) Fonte: mozilla dev docs
Sean

30

Oppure puoi usare querySelectorAllquale restituisce NodeList :

document.querySelectorAll('.myclass').forEach(...)

Supportato dai browser moderni (incluso Edge, ma non IE):
posso usare querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()


4
Tieni presente la penalità prestazionale per getElementByClassName
Szabolcs Páll

3
La penalità di prestazione è trascurabile rispetto ad altre attività più intense come la modifica del DOM. Se eseguo 60.000 di questi in 1 millisecondo , sono abbastanza sicuro che non sarà un problema per un uso ragionevole :)
icl7126

1
Hai collegato il benchmark sbagliato. Ecco quello corretto measurethat.net/Benchmarks/Show/4076/0/… Ho appena eseguito sul mio telefono di fascia bassa, ho 160k / s vs 380k / s. Dato che hai menzionato la manipolazione del DOM, ecco che anche measurethat.net/Benchmarks/Show/5705/0/… ha 50k / s contro 130k / s. Come vedi, è ancora più lento manipolare il DOM, probabilmente a causa della staticità di NodeList (come menzionato da altri). Ancora trascurabile nella maggior parte dei casi d'uso, ma quasi 3 volte più lento.
Szabolcs Páll

14

Modifica: sebbene il tipo restituito sia cambiato nelle nuove versioni di HTML (vedi la risposta aggiornata di Tim Down), il codice seguente funziona ancora.

Come altri hanno già detto, è un NodeList. Ecco un esempio completo e funzionante che puoi provare:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

Funziona in IE 9, FF 5, Safari 5 e Chrome 12 su Win 7.


9

Il risultato getElementsByClassName()non è un array, ma un oggetto simile ad un array . In particolare si chiama un HTMLCollection, da non confondere NodeList( che ha il suo forEach()metodo ).

Un modo semplice con ES2015 per convertire un oggetto simile a un array da usare con Array.prototype.forEach()quello che non è stato ancora menzionato è quello di usare l'operatore spread o la sintassi spread :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});

2
Penso che questo sia davvero il modo giusto per farlo nei browser moderni. Questo è l'esatta sintassi di diffusione del caso d'uso creata per risolvere.
Matt Korostoff,


3

Come già detto, getElementsByClassNamerestituisce un HTMLCollection , che è definito come

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

In precedenza, alcuni browser restituivano invece un NodeList .

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

La differenza è importante, poiché DOM4 ora definisce NodeList come iterabile.

Secondo la bozza di Web IDL ,

Gli oggetti che implementano un'interfaccia dichiarata supporto iterabile vengono ripetuti per ottenere una sequenza di valori.

Nota : Nel linguaggio ECMAScript vincolante, un'interfaccia che è iterabile avrà “voci”,, “chiavi” “foreach”, “valori” e @@ iteratore proprietà sul suo oggetto prototipo di interfaccia .

Ciò significa che, se si desidera utilizzare forEach, è possibile utilizzare un metodo DOM che restituisce un NodeList , come querySelectorAll.

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

Nota che questo non è ancora ampiamente supportato. Vedi anche Per ogni metodo di Node.childNodes?


1
Chrome 49 ritornoforEach in not a function
Vitaly Zdanevich,

@VitalyZdanevich Prova Chromium 50
Oriol,

Su Chrome 50 sto ricevendodocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich il

@VitalyZdanevich Ha funzionato su Chromium 50 e funziona ancora su Chromium 53. Forse non è stato considerato abbastanza stabile per essere spedito su Chrome 50.
Oriol


1

Questo è il modo più sicuro:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);

0

getElementsByClassNamerestituisce HTMLCollection nei browser moderni.

che è un oggetto simile a una matrice simile agli argomenti che è ripetibile con il for...ofciclo, vedi di seguito cosa dice il documento MDN al riguardo:

L' istruzione for ... of crea un ciclo che scorre su oggetti iterabili , inclusi: String incorporati, Array, Oggetti simili a array (ad es. Argomenti o NodeList), TypedArray, Map, Set e iterable definiti dall'utente. Richiama un hook di iterazione personalizzato con istruzioni da eseguire per il valore di ogni proprietà distinta dell'oggetto.

esempio

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}

Non è così, secondo Typescript:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Turtles Are Cute

@TurtlesAreCute, qui OP utilizza javascript non dattiloscritto e ho risposto in base alla raccomandazione di vanilla js, quindi in dattiloscritto può essere una soluzione diversa per il problema.
Haritsinh Gohil

@TurtlesAreCute, A proposito funziona anche in dattiloscritto, ma devi menzionare il giusto tipo di variabile che contiene elementi di una particolare classe css, quindi può lanciarla di conseguenza, per i dettagli vedi questa risposta .
Haritsinh Gohil

0

Ecco un test che ho creato su jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

La versione più performante in Chrome e Firefox è il buon vecchio per loop in combinazione con document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

In Safari questa variante è il vincitore:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

Se vuoi la variante più performante per tutti i browser, potrebbe essere questa:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
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.