web-dev-qa-db-fra.com

Boucle For pour les éléments HTMLCollection

J'essaie de définir get id de tous les éléments dans une HTMLCollectionOf. J'ai écrit le code suivant:

var list = document.getElementsByClassName("events");
console.log(list[0].id);
for (key in list) {
    console.log(key.id);
}

Mais j'ai la sortie suivante dans la console:

event1
undefined

ce qui n'est pas ce à quoi je m'attendais. Pourquoi la deuxième sortie de console est-elle undefined alors que la première sortie de console est event1?

247
user2953119

Résumé (ajouté en décembre 2018)

N'utilisez jamais for/in pour itérer une liste de noeuds ou une collection HTMLC. Les raisons pour l'éviter sont décrites ci-dessous.

Toutes les versions récentes des navigateurs modernes (Safari, Firefox, Chrome, Edge) prennent en charge l'itération for/of sur des listes DOM telles que nodeList ou HTMLCollection.

Voici un exemple:

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

Pour inclure les anciens navigateurs (y compris des choses comme IE), cela fonctionnera partout:

var list= document.getElementsByClassName("events");
for (var i = 0; i < list.length; i++) {
    console.log(list[i].id); //second console output
}

Explication de la raison pour laquelle vous ne devriez pas utiliser for/in

for/in est destiné à itérer les propriétés d'un objet. Cela signifie qu'il renverra toutes les propriétés itérables d'un objet. Bien que cela puisse sembler fonctionner pour un tableau (renvoyer des éléments de tableau ou des éléments de pseudo-tableaux), il peut également renvoyer d'autres propriétés de l'objet qui ne correspondent pas à ce que vous attendez des éléments de type tableau. Et, devinez quoi, un objet HTMLCollection ou nodeList peut avoir d'autres propriétés qui seront retournées avec une itération for/in. Je viens d’essayer cela dans Chrome et en le répétant, vous récupérerez les éléments de la liste (index 0, 1, 2, etc.), mais également les propriétés length et item. L'itération for/in ne fonctionnera tout simplement pas pour une collection HTMLCollection.


Voir http://jsfiddle.net/jfriend00/FzZ2H/ pour savoir pourquoi vous ne pouvez pas itérer une collection HTMLC avec for/in.

Dans Firefox, votre itération for/in renverrait ces éléments (toutes les propriétés itérables de l'objet):

0
1
2
item
namedItem
@@iterator
length

Si tout va bien, vous pouvez maintenant voir pourquoi vous voulez utiliser for (var i = 0; i < list.length; i++) à la place, de sorte que vous n'obtenez que 0, 1 et 2 dans votre itération.


Vous trouverez ci-dessous une évolution de la manière dont les navigateurs ont évolué au cours de la période 2015-2018, en vous offrant des moyens supplémentaires d’itérer. Aucun de ceux-ci n'est maintenant nécessaire dans les navigateurs modernes, car vous pouvez utiliser les options décrites ci-dessus.

Mise à jour pour ES6 en 2015

Array.from() a été ajouté à ES6 pour convertir une structure semblable à un tableau à un tableau réel. Cela permet d’énumérer directement une liste comme ceci:

"use strict";

Array.from(document.getElementsByClassName("events")).forEach(function(item) {
   console.log(item.id);
});

Démonstration de travail (dans Firefox, Chrome et Edge à compter d'avril 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Mise à jour de l'ES6 en 2016

Vous pouvez maintenant utiliser la construction ES6 pour/de avec une NodeList et une HTMLCollection en ajoutant simplement ceci à votre code:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

Ensuite, vous pouvez faire:

var list = document.getElementsByClassName("events");
for (var item of list) {
    console.log(item.id);
}

Cela fonctionne dans la version actuelle de Chrome, Firefox et Edge. Cela fonctionne car il lie l'itérateur Array aux prototypes NodeList et HTMLCollection de sorte que, lorsque pour/des itère, il utilise l'itérateur Array pour les itérer.

Démo de travail: http://jsfiddle.net/jfriend00/joy06u4e/ .


Deuxième mise à jour pour ES6 en décembre 2016

Depuis décembre 2016, la prise en charge de Symbol.iterator a été intégrée à Chrome v54 et à Firefox v50, de sorte que le code ci-dessous fonctionne de manière autonome. Il n'est pas encore intégré à Edge.

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

Démo de travail (sous Chrome et Firefox): http://jsfiddle.net/jfriend00/3ddpz8sp/

Troisième mise à jour pour ES6 en décembre 2017

Depuis décembre 2017, cette fonctionnalité fonctionne dans Edge 41.16299.15.0 pour un nodeList dans document.querySelectorAll(), mais pas un HTMLCollection dans document.getElementsByClassName(). Vous devez donc affecter manuellement l'itérateur à l'utiliser dans Edge pour un HTMLCollection. La raison pour laquelle ils ont résolu un type de collection, mais pas l’autre, est un mystère total. Toutefois, vous pouvez au moins utiliser le résultat de document.querySelectorAll() avec la syntaxe ES6 for/of dans les versions actuelles d'Edge maintenant.

J'ai également mis à jour le fichier jsFiddle ci-dessus afin qu'il teste HTMLCollection et nodeList séparément et capture la sortie dans le fichier jsFiddle lui-même.

Quatrième mise à jour pour ES6 en mars 2018

Comme indiqué précédemment, le support Symbol.iterator a également été intégré à Safari, vous pouvez donc utiliser for (let item of list) pour document.getElementsByClassName() ou document.querySelectorAll().

Cinquième mise à jour pour ES6 en avril 2018

Apparemment, le support pour l'itération d'une HTMLCollection avec for/of arrivera dans Edge 18 à l'automne 2018. 

Sixième mise à jour pour ES6 en novembre 2018

Je peux confirmer qu'avec Microsoft Edge v18 (inclus dans la mise à jour de l'automne 2018 de Windows), vous pouvez désormais effectuer une itération à la fois sur une collection HTMLCollection et sur une liste de noeuds avec pour/de dans Edge.

Ainsi, tous les navigateurs modernes contiennent désormais une prise en charge native de l'itération for/of des objets HTMLCollection et NodeList.

528
jfriend00

Vous ne pouvez pas utiliser forin sur NodeLists ou HTMLCollections. Cependant, vous pouvez utiliser certaines méthodes Array.prototype, tant que vous les .call() et transmettez NodeList ou HTMLCollection comme this.

Donc, considérez ce qui suit comme une alternative à la boucle for de jfriend00 :

var list= document.getElementsByClassName("events");
[].forEach.call(list, function(el) {
    console.log(el.id);
});

Il existe un bon article sur MDN qui couvre cette technique. Notez leur avertissement concernant la compatibilité du navigateur:

[...] en passant un objet hôte (comme une NodeList) en tant que this à une méthode native (telle que forEach) n’est pas garantie de fonctionner dans tous les navigateurs et est connu pour échouer dans certains.

Ainsi, bien que cette approche soit pratique, une boucle for peut être la solution la plus compatible avec les navigateurs.

_/Mise à jour (30 août 2014): Vous pourrez éventuellement utiliser ES6 for/of !

var list = document.getElementsByClassName("events");
for (el of list)
  console.log(el.id);

Il est déjà pris en charge dans les versions récentes de Chrome et Firefox.

68
evanrmurphy

Dans ES6, vous pouvez faire quelque chose comme [...collection] ou Array.from(collection),

let someCollection = document.querySelectorAll(someSelector)
[...someCollection].forEach(someFn) 
//or
Array.from(collection).forEach(someFn)
36
mido

vous pouvez ajouter ces deux lignes:

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

HTMLCollection est renvoyé par getElementsByClassName et getElementsByTagName  

NodeList est renvoyé par querySelectorAll

Comme cela, vous pouvez faire un forEach:

var selections = document.getElementsByClassName('myClass');

/* alternative :
var selections = document.querySelectorAll('.myClass');
*/

selections.forEach(function(element, i){
//do your stuffs
});
10
mmnl

J'ai eu un problème d'utilisation de forEach dans IE 11 et aussi Firefox 49

J'ai trouvé une solution de contournement comme celle-ci

Array.prototype.slice.call(document.getElementsByClassName("events")).forEach(function (key) {
        console.log(key.id);
    }
6
mamosek

Depuis mars 2016, dans Chrome 49.0, for...of fonctionne pour HTMLCollection:

this.headers = this.getElementsByTagName("header");

for (var header of this.headers) {
    console.log(header); 
}

Voir ici la documentation .

Mais cela ne fonctionne que si vous appliquez la solution suivante before à l'aide du for...of:

HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

La même chose est nécessaire pour utiliser for...of avec NodeList:

NamedNodeMap.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

Je crois/espère que for...of fonctionnera bientôt sans la solution de contournement ci-dessus. La question ouverte est ici:

https://bugs.chromium.org/p/chromium/issues/detail?id=401699

Mise à jour: Voir le commentaire de Expenzor ci-dessous: Ce problème a été résolu en avril 2016. Il n'est pas nécessaire d'ajouter HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]. sur une collection HTMLC avec pour ... de

4
MarcG

Alternative à Array.from est d'utiliser Array.prototype.forEach.call

pour chaque: Array.prototype.forEach.call(htmlCollection, i => { console.log(i) });

carte: Array.prototype.map.call(htmlCollection, i => { console.log(i) });

ect ...

3
holmberd

Il n'y a aucune raison d'utiliser les fonctionnalités es6 pour échapper à la boucle for si vous utilisez IE9 ou une version ultérieure.

Dans ES5, il existe deux bonnes options. Tout d’abord, vous pouvez «emprunter» le nom Array de forEach comme evan mentionne .

Mais encore mieux ...

Utilisez Object.keys(), qui do a forEach

Object.keys est essentiellement équivalent à faire un for... in avec un HasOwnProperty, mais est plus lisse.

var eventNodes = document.getElementsByClassName("events");
Object.keys(eventNodes).forEach(function (key) {
    console.log(eventNodes[key].id);
});
2
ruffin

Sur le bord

if(!NodeList.prototype.forEach) {
  NodeList.prototype.forEach = function(fn, scope) {
    for(var i = 0, len = this.length; i < len; ++i) {
      fn.call(scope, this[i], i, this);
    }
  }
}
1
Tiago Pertile

Vous voulez le changer en 

var list= document.getElementsByClassName("events");
console.log(list[0].id); //first console output
for (key in list){
    console.log(list[key].id); //second console output
}
0
Andy897

Contournement facile que j'utilise toujours

let list = document.getElementsByClassName("events");
let listArr = Array.from(list)

Après cela, vous pouvez exécuter n’importe quelle méthode Array de votre choix.

listArr.map(item => console.log(item.id))
listArr.forEach(item => console.log(item.id))
listArr.reverse()
0
Creeptosis

si vous utilisez des versions plus anciennes d'ES (ES5 par exemple), vous pouvez utiliser as any:

for (let element of elementsToIterate as any) {
      console.log(element);
}

0
Alon Gouldman