Boucle pour les éléments HTMLCollection

406

J'essaie de définir get id de tous les éléments dans un 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 obtenu la sortie suivante dans la console:

event1
undefined

ce qui n'est pas ce que j'attendais. Pourquoi la deuxième sortie de la console est- undefinedelle la première sortie de la console event1?

Neurone
la source
23
pourquoi le titre dit "foreach" alors que la question porte sur le for ... in statement? Je suis venu ici par accident lors d'une recherche sur Google.
mxt3
@ mxt3 Eh bien, à ma connaissance, c'était un anologue de la boucle for-each en Java (fonctionnait de la même façon).
1
@ mxt3 je pensais la même chose! Mais après avoir lu la réponse acceptée, j'ai trouvé cette ligne, qui a résolu mon problème foreach, en utilisant Array.from ():Array.from(document.getElementsByClassName("events")).forEach(function(item) {
HoldOffHunger

Réponses:

838

En réponse à la question d'origine, vous utilisez for/inincorrectement. Dans votre code, keyest l'index. Donc, pour obtenir la valeur du pseudo-tableau, vous devez le faire list[key]et pour obtenir l'id, vous le feriez list[key].id. Mais, vous ne devriez pas faire cela for/inen premier lieu.

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

Ne jamais utiliser for/inpour itérer une nodeList ou une HTMLCollection. Les raisons de l'éviter sont décrites ci-dessous.

Toutes les versions récentes des navigateurs modernes (Safari, Firefox, Chrome Edge) tout l' appui for/ofitération sur liste DOM tel nodeListou HTMLCollection.

Voici un exemple:

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

Pour inclure des navigateurs plus anciens (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 des raisons pour lesquelles vous ne devriez pas utiliser for/in

for/inest 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 qu'il puisse sembler fonctionner pour un tableau (renvoyant des éléments de tableau ou des éléments de pseudo-tableau), 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 HTMLCollectionou un nodeListobjet peut tous deux avoir d'autres propriétés qui seront retournées avec une for/initération. Je viens de l'essayer dans Chrome et de l'itérer comme vous l'avez fait, il récupérera les éléments de la liste (index 0, 1, 2, etc ...), mais récupérera également les propriétés lengthet item. L' for/initération ne fonctionnera tout simplement pas pour une HTMLCollection.


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

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

0
1
2
item
namedItem
@@iterator
length

Avec un peu de chance, vous pouvez maintenant voir pourquoi vous souhaitez utiliser à la for (var i = 0; i < list.length; i++)place, donc vous obtenez juste 0, 1et 2dans votre itération.


Vous trouverez ci-dessous une évolution de la façon dont les navigateurs ont évolué au cours de la période 2015-2018, vous offrant des moyens supplémentaires d'itérer. Aucun de ces éléments n'est désormais nécessaire dans les navigateurs modernes, car vous pouvez utiliser les options décrites ci-dessus.

Mise à jour pour ES6 en 2015

Ajouté à ES6 Array.from(), il convertira une structure de type tableau en un tableau réel. Cela permet d'énumérer une liste directement comme ceci:

"use strict";

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

Démo fonctionnelle (dans Firefox, Chrome et Edge à partir d'avril 2016): https://jsfiddle.net/jfriend00/8ar4xn2s/


Mise à jour pour ES6 en 2016

Vous pouvez maintenant utiliser la construction ES6 for / of avec a NodeListet an HTMLCollectionen 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 attache l'itérateur Array aux prototypes NodeList et HTMLCollection de sorte que lorsque pour / de les 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 Symbol.iteratorprise en charge a été intégrée à Chrome v54 et Firefox v50 afin que le code ci-dessous fonctionne seul. Il n'est pas encore intégré pour Edge.

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

Démo de travail (dans 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 nodeListas in document.querySelectorAll(), mais pas un HTMLCollectionas in document.getElementsByClassName(), vous devez donc affecter manuellement l'itérateur pour l'utiliser dans Edge for an HTMLCollection. C'est un mystère total pourquoi ils ont fixé un type de collection, mais pas l'autre. Mais, vous pouvez au moins utiliser le résultat de la syntaxe document.querySelectorAll()ES6 for/ofdans les versions actuelles d'Edge maintenant.

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

Quatrième mise à jour pour ES6 en mars 2018

Par mesqueeeb, la Symbol.iteratorprise en charge a également été intégrée à 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'un HTMLCollectionavec for/ofarrivera à 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 Windows de l'automne 2018), vous pouvez désormais itérer à la fois une HTMLCollection et une NodeList avec for / of dans Edge.

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

jfriend00
la source
1
Merci pour les mises à jour très détaillées que JS a mises à jour. Cela aide les débutants à comprendre ces conseils dans le contexte d'autres articles / publications qui ne s'appliquent qu'à une version spécifique de JS.
brownmagik352
Réponse exceptionnelle, support de mise à jour incroyable .. Merci.
Cossacksman
80

Vous ne pouvez pas utiliser for/ insur NodeLists ou HTMLCollections. Cependant, vous pouvez utiliser certaines Array.prototypeméthodes, tant que vous .call()les utilisez et passez le NodeListou HTMLCollectioncomme this.

Considérez donc ce qui suit comme une alternative à la forboucle de jfriend00 :

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

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

[...] le passage d'un objet hôte (comme a NodeList) thisà une méthode native (comme forEach) n'est pas garanti pour fonctionner dans tous les navigateurs et est connu pour échouer dans certains.

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

Mise à jour (30 août 2014): Finalement, vous pourrez utiliser ES6 for/of !

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

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

evanrmurphy
la source
1
Très agréable! J'ai utilisé cette technique pour obtenir les valeurs des options sélectionnées à partir de a <select multiple>. Exemple:[].map.call(multiSelect.selectedOptions, function(option) { return option.value; })
XåpplI'-I0llwlg'I -
1
Je cherchais une solution ES2015 à cela, alors merci d'avoir confirmé que cela for ... offonctionne.
Richard Turner
60

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)

Par exemple:-

    navDoms = document.getElementsByClassName('nav-container');
    Array.from(navDoms).forEach(function(navDom){
     //implement function operations
    });
mido
la source
@DanielM suppose que ce que j'ai fait est de cloner superficiellement une structure de type tableau
mido
Je vois, merci - maintenant j'ai trouvé la documentation que je cherchais: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
DanielM
Je l'utilise toujours, beaucoup plus facilement aux yeux qu'Array.from, je me demande simplement s'il présente des performances ou des inconvénients de mémoire considérables. Par exemple, si j'ai besoin d'itérer les cellules d'une ligne de tableau, j'utilise un [...row.cells].forEachau lieu de faire unrow.querySelectorAll('td')
Mojimi
16

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 ça, vous pouvez faire un forEach:

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

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

selections.forEach(function(element, i){
//do your stuffs
});
mmnl
la source
Cette réponse semble si efficace. Quelle est la prise?
Peheje
2
Le hic, c'est que cette solution ne fonctionne pas sur IE11! Bonne solution cependant.
Rahul Gaba
2
Notez que c'est NodeListdéjà le casforEach() .
Franklin Yu
7

J'ai eu un problème avec 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);
    }
Mamosek
la source
Excellente solution pour IE11!
C'était
6

Alternative à Array.fromest d'utiliserArray.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 ...

holmberd
la source
6

Il n'y a aucune raison d'utiliser les fonctionnalités es6 pour échapper au forbouclage si vous êtes sur IE9 ou supérieur.

Dans ES5, il existe deux bonnes options. D' abord, vous pouvez « » Array« s forEachcomme evan mentionne .

Mais encore mieux ...

L' utilisation Object.keys(), qui ne dispose forEachet les filtres à « propres propriétés » automatiquement.

Autrement dit, Object.keysest essentiellement équivalent à faire un for... inavec un HasOwnProperty, mais est beaucoup plus fluide.

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

Depuis mars 2016, dans Chrome 49.0, for...offonctionne 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 de contournement suivante avant d' utiliser for...of:

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

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

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

Je crois / espère for...offonctionnera bientôt sans la solution de contournement ci-dessus. Le problème ouvert est ici:

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

Mise à jour: Voir le commentaire d'Expenzor ci-dessous: Cela a été corrigé en avril 2016. Vous n'avez pas besoin d'ajouter HTMLCollection.prototype [Symbol.iterator] = Array.prototype [Symbol.iterator]; pour itérer sur une HTMLCollection avec pour ... de

MarcG
la source
4
Cela a été corrigé en avril 2016. Vous n'avez pas besoin d'ajouter HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];d'itération sur un HTMLCollectionavec for...of.
Expenzor
3

On Edge

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);
    }
  }
}
Tiago Pertile
la source
2

Solution de contournement facile que j'utilise toujours

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

Après cela, vous pouvez exécuter toutes les méthodes de tableau souhaitées sur la sélection

listArr.map(item => console.log(item.id))
listArr.forEach(item => console.log(item.id))
listArr.reverse()
Creeptose
la source
1

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);
}
Alon Gouldman
la source
0

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
}
Andy897
la source
5
Pour info, voyez ma réponse pour savoir pourquoi cela ne fonctionnera pas correctement. Le for (key in list)renverra plusieurs propriétés du HTMLCollectionqui ne sont pas censées être des éléments de la collection.
jfriend00