JS: itération sur le résultat de getElementsByClassName à l'aide de Array.forEach

240

Je veux parcourir certains éléments DOM, je fais ceci:

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

mais je reçois une erreur:

document.getElementsByClassName ("myclass"). forEach n'est pas une fonction

J'utilise Firefox 3, donc je sais que les deux getElementsByClassNameet Array.forEachsont présents. Cela fonctionne bien:

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

Est le résultat d' getElementsByClassNameun tableau? Sinon, c'est quoi?

Steve Claridge
la source

Réponses:

384

Non. Comme spécifié dans DOM4 , c'est un HTMLCollection(dans les navigateurs modernes, au moins. Les navigateurs plus anciens ont renvoyé un NodeList).

Dans tous les navigateurs modernes (à peu près tout autre IE <= 8), vous pouvez appeler la forEachméthode Array , en lui passant la liste des éléments (que ce HTMLCollectionsoit ou NodeList) comme thisvaleur:

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

Si vous êtes en mesure de pouvoir utiliser ES6 (c'est-à-dire que vous pouvez ignorer Internet Explorer en toute sécurité ou que vous utilisez un transpileur ES5), vous pouvez utiliser Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Tim Down
la source
29
Pas besoin de le convertir d'abord en tableau. Utilisez simplement [].forEach.call(elsArray, function () {...}).
kay - SE is evil
1
Ce n'est PAS une NodeList. C'est un objet de type tableau. Je ne pense même pas qu'il ait un type d'instance. querySelectorAllLa méthode renvoie cependant une NodeList.
Maksim Vi.
2
@MaksimVi. Vous avez absolument raison: DOM4 spécifie que document.getElementsByClassName()doit retourner un HTMLCollection(qui est très similaire mais pas un NodeList). Merci d'avoir signalé l'erreur.
Tim Down
@MaksimVi .: Je me demande si cela a changé à un moment donné. Je vérifie généralement ces choses.
Tim Down
1
@TimDown, merci pour le HTMLCollectionconseil. Maintenant, je peux enfin utiliser HTMLCollection.prototype.forEach = Array.prototype.forEach;dans mon code.
Maksim Vi.
70

Vous pouvez utiliser Array.frompour convertir la collection en tableau, ce qui est beaucoup plus propre que Array.prototype.forEach.call:

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

Dans les anciens navigateurs qui ne prennent pas en charge Array.from, vous devez utiliser quelque chose comme Babel.


ES6 ajoute également cette syntaxe:

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

Reste la déstructuration avec des ...travaux sur tous les objets de type tableau, non seulement les tableaux eux-mêmes, mais une bonne vieille syntaxe de tableau est utilisée pour construire un tableau à partir des valeurs.


Alors que la fonction alternative querySelectorAll(qui rend un peu getElementsByClassNameobsolète) retourne une collection qui a forEachnativement, d'autres méthodes comme mapou filtermanquent, donc cette syntaxe est toujours utile:

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

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Athari
la source
6
Remarque: sans transpiler comme suggéré (Babel), ce n'est PAS compatible dans IE <Edge, Opera, Safari <9, navigateur Android, Chrome pour Android, ... etc) Source: mozilla dev docs
Sean
30

Ou vous pouvez utiliser querySelectorAllce qui retourne NodeList :

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

Pris en charge par les navigateurs modernes (y compris Edge, mais pas IE):
puis-je utiliser querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()

icl7126
la source
4
Gardez à l'esprit la pénalité de performance sur getElementByClassName
Szabolcs Páll
3
La pénalité de performance est négligeable par rapport à d'autres tâches plus intensives comme la modification de DOM. Si j'en exécute 60 000 en 1 milliseconde , je suis sûr que ce ne sera pas un problème pour une utilisation raisonnable :)
icl7126
1
Vous avez lié le mauvais repère. Voici la bonne mesure que.net/Benchmarks/Show/4076/0/… Je viens de lancer sur mon téléphone bas de gamme, j'ai obtenu 160k / s contre 380k / s. Puisque vous avez mentionné la manipulation DOM, voici que trop mesure que.net/Benchmarks/Show/5705/0/… a obtenu 50k / s contre 130k / s. Comme vous le voyez, il est encore plus lent de manipuler DOM, probablement en raison de la statique de NodeList (comme mentionné par d'autres). Toujours non négociables dans la plupart des cas d'utilisation, mais presque 3 fois plus lent néanmoins.
Szabolcs Páll
14

Modifier: Bien que le type de retour ait changé dans les nouvelles versions de HTML (voir la réponse mise à jour de Tim Down), le code ci-dessous fonctionne toujours.

Comme d'autres l'ont dit, c'est une NodeList. Voici un exemple complet et fonctionnel que vous pouvez essayer:

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

Cela fonctionne dans IE 9, FF 5, Safari 5 et Chrome 12 sur Win 7.

james.garriss
la source
9

Le résultat de getElementsByClassName()n'est pas un tableau, mais un objet de type tableau . Plus précisément, il s'appelle un HTMLCollection, à ne pas confondre avec NodeList( qui a sa propre forEach()méthode ).

Une façon simple avec ES2015 de convertir un objet de type tableau pour une utilisation avec Array.prototype.forEach()celui qui n'a pas encore été mentionné consiste à utiliser l'opérateur d' étalement ou la syntaxe d'étalement :

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

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
la source
2
Je pense que c'est vraiment la bonne façon de le faire dans les navigateurs modernes. Il s'agit de la syntaxe de propagation du cas d'utilisation exacte qui a été créée pour résoudre.
Matt Korostoff
3

Comme déjà dit, getElementsByClassNamerenvoie une HTMLCollection , qui est définie comme

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

Auparavant, certains navigateurs renvoyaient une NodeList à la place.

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

La différence est importante, car DOM4 définit désormais NodeList comme étant itérables.

Selon le projet Web IDL ,

Les objets implémentant une interface déclarée itérable prennent en charge l'itération pour obtenir une séquence de valeurs.

Remarque : Dans la liaison de langage ECMAScript, une interface qui est itérable aura des propriétés «entrées», «pour chaque», «clés», «valeurs» et @@ itérateur sur son objet prototype d'interface .

Cela signifie que si vous souhaitez utiliser forEach , vous pouvez utiliser une méthode DOM qui renvoie une NodeList , comme querySelectorAll.

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

Notez que ce n'est pas encore largement pris en charge. Regarde aussi également la méthode forEach de Node.childNodes?

Oriol
la source
1
Retour de Chrome 49forEach in not a function
Vitaly Zdanevich
@VitalyZdanevich Try Chromium 50
Oriol
Sur Chrome 50, je reçoisdocument.querySelectorAll(...).forEach is not a function
Vitaly Zdanevich
@VitalyZdanevich Il a fonctionné sur Chromium 50, et fonctionne toujours sur Chromium 53. Peut-être qu'il n'était pas considéré comme suffisamment stable pour être expédié à Chrome 50.
Oriol
1

Il ne renvoie pas un Array, il renvoie un NodeList .

reko_t
la source
1

Voici le moyen le plus sûr:

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

getElementsByClassNamerenvoie HTMLCollection dans les navigateurs modernes.

qui est un objet de type tableau similaire aux arguments qui peut être itéré par for...ofboucle, voir ci-dessous ce que le doc MDN en dit:

L' instruction for ... of crée une boucle itérative sur les objets itérables , y compris: String, Array, objets de type tableau intégrés (par exemple, arguments ou NodeList), TypedArray, Map, Set et les itérables définis par l'utilisateur. Il appelle un hook d'itération personnalisé avec des instructions à exécuter pour la valeur de chaque propriété distincte de l'objet.

exemple

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Haritsinh Gohil
la source
Pas du tout, selon Typescript:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
tortues sont mignonnes
@TurtlesAreCute, ici OP utilise javascript et non typographique et j'ai répondu selon la recommandation de vanilla js donc en tapuscrit, cela peut être une solution différente pour le problème.
Haritsinh Gohil
@TurtlesAreCute, Soit dit en passant, cela fonctionne également en tapuscrit, mais vous devez mentionner le bon type de variable qui contient l'élément d'une classe CSS particulière, afin qu'il puisse le convertir en conséquence, pour plus de détails, voir cette réponse .
Haritsinh Gohil
0

Voici un test que j'ai créé sur jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

La version la plus performante de Chrome et Firefox est la bonne vieille boucle pour en combinaison avec document.getElementsByClassName:

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

Dans Safari, cette variante est gagnante:

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

Si vous voulez la variante la plus performante pour tous les navigateurs, ce pourrait être celle-ci:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
StefanSL
la source