JavaScript forEach : la référence ultime. Tous les exemples et alternatives.

Pour maîtriser la méthode forEach en JavaScript et l’utiliser dans les meilleures conditions :

Vous saurez utiliser ses forces, repérer ses faiblesses, et choisir des boucles plus adaptées selon les cas.

Comment itérer sur un Array, un Set, une Map (en obtenant ses clés), et les autres objets qui ressemble à des tableaux… mais n’en sont pas. Peut-on faire un break ou continue comme dans une  boucle classique, le forEach JavaScript est-il synchrone ? Je réponds à toutes vos questions !

Syntaxe de forEach

La méthode JavaScript forEach s’applique à un tableau, une map ou un set (et pas seulement un tableau).
forEach prend en paramètre une fonction callback, et l’execute en lui passant chaque élément de la collection.

Syntaxe basique pour forEach

La méthode forEach et la fonction callback acceptent des paramètres supplémentaires :

Syntaxe complète pour forEach

La fonction callback peut recevoir deux paramètres optionnels :

  • L’index en cours d’itération.
    D’autant plus pratique que, si besoin de le gérer après coup, il suffit de l’ajouter à la signature du callback.
    Dans le cas d’une Map, l’index est en faite la clé courante.
  • La collection sur laquelle s’effectue le forEach
    dans le cas où le callback n’y avait pas accès.

Et le forEach lui même accepte un second paramètre :

  • Une valeur pour this
    que pourra utiliser un callback de type fonction classique (inutile si le callback est une arrow function).

Reference Rapide forEachPas le temps de lire un long article ?
Téléchargez la Référence Rapide forEach
+ en bonus : le Guide Facile des Boucles

 

Règles

Pas de retour

forEach n’est pas conçu pour produire une valeur de retour. La méthode retourne undefined.
Elle n’est donc pas chainable avec d’autres méthodes JavaScript qui arriveraient après forEach.

Synchrone

Si la fonction callback prends du temps, attention, l’exécution est bloquante (synchrone).

Jusqu’à la fin

Pas moyen de sortir de la boucle avant la fin, ni avec un break ni avec un return dans le callback.
Pas moyen d’utiliser continue, mais un return peut faire l’affaire.

Modification en cours de route

Dans le cas de modification du tableau ou d’un élément après l’appel de la méthode :
des éléments peuvent ne pas être pris en compte (la méthode ne fait pas de copie du tableau avant d’itérer).

Valeur de this

Pour utiliser this dans le callback, et si ce dernier est une function classique, il faut fournir une valeur pour this en argument de forEach. Sinon, c’est undefined qui sera utilisé dans le callback.

Dans le cas où le callback est une arrow function, il n’y a alors pas besoin de spécifier de valeur pour this.

Exemples JavaScript avec forEach

Array

Itérer sur un Array, avec une fonction anonyme, est très simple :


const array = [1, 5, 10];
array.forEach(function(currentValue) {
    console.log(currentValue);
});

Et c’est encore plus simple avec une fonction en notation « arrow » (dite « fat arrow function ») :


const array = [1, 5, 10];
array.forEach(currentValue => console.log(currentValue));

Map

Une itération sur les clés et valeurs d’une Map est tout aussi facile.
La clé de chaque élément de la Map est donnée par l’index reçu en 2ème argument :


const map = new Map();
const keyObject = { desc: 'an object as a key'};

map.set('key 1', 'value 1');
map.set('key 2', 'value 2');
map.set(keyObject, 'value for keyObject');

map.forEach((value, index) => 
    console.log(`at key: ${index}, value is: ${value}`));

forEach on map

Set

De même pour parcourir un Set :


const set = new Set(['a', 'b', 'c']);
set.forEach(value => console.log(value));

Callback exploitant tous les paramètres

Pour utiliser dans le callback l’index courant et le tableau d’origine :


function logItem(value, index, array) {
    let log = value + ' at ' + index;

    if (index != array.length - 1) {
        log += ', ';
    }
    else {
        log += ', done.';
    }
    console.log(log);
}
const array = ['a', 'b', 'c'];

array.forEach(logItem);

foreach, callback with all parameters

Passage de this

Si le callback est une fonction classique

Si le callback est n’est pas une arrow function, et que le callback a besoin d’accéder à this, this doit être fourni à forEach en 2ème argument :


function Counter() {
    this.even = 0;
    this.odd = 0;
}

Counter.prototype.count = function (numbers) {
    numbers.forEach(function (number) {
        if (number % 2 === 0) {
            this.even++;
        }
        else {
            this.odd++;
        }
    }, this);
};

const counter = new Counter();
counter.count([2, 4, 6, 7, 8]);
console.log(`${counter.even} even, ${counter.odd} odd`);

forEach with this

Si le callback est une fonction arrow

L’arrow function peut accéder à this sans qu’il y ait besoin de le passer en argument (les arrow functions lient la valeur de this lexicalement) :


function Counter() {
    this.even = 0;
    this.odd = 0;
}

Counter.prototype.count = function (numbers) {
    numbers.forEach((number) => {
        if (number % 2 === 0) {
            this.even++;
        }
        else {
            this.odd++;
        }
    } /* no need for a 'this' argument */);
};

const counter = new Counter();
counter.count([2, 4, 6, 7, 8]);
console.log(`${counter.even} even, ${counter.odd} odd`);

Objet JSON

Il est possible de parcourir un objet JSON, en passant par Object.keys qui renvoie un tableau des propriétés de l’objet.
Puis on accède à chaque valeur en utilisant la propriété comme clé :

forEach sur objet JSON


const json = {
    'propA': 'value A',
    'propB': 'value B'
};

Object.keys(json).forEach(function(prop){
    console.log(`${prop} : ${json[prop]}`);    
});

forEach sur un objet JSON

Mais bon, c’est quand même plus facile avec for..in :


for (let prop in json) {
    console.log(`${prop} : ${json[prop]}`);
};

Tableau « clairsemé »

S’il n’y a pas de donnée présente à un index, la fonction callback n’est pas appelée pour cet index :


const sparse = [0, 10, , 30]; // no value at index 2
sparse.forEach((value, index) => 
    console.log(`at index: ${index}, value is: ${value}`));

forEach sur un Array clairsemé

break dans un forEach

Il n’existe pas d’instruction break comme dans une boucle classique JavaScript for ou for..of.

Quant à faire un throw d’erreur juste pour sortir de la boucle, cela n’est vraiment pas recommandé.

Mais, avec les méthodes cousines, certains cas peuvent être résolus.

Avec la méthode every

Avec every, les itérations s’arrêtent lorsqu’une condition n’est plus remplie :


const breakable = [1, 5, 10, 15, 20];

function breakAbove10(element) {
    const lte10 = element <= 10;
    if (lte10) console.log(element);
    else console.log('> 10, break');

    return lte10;
}

breakable.every(breakAbove10);

Every, to break loop

Ce qui fourni un comportement comparable à un break.

Une variante est possible avec some, comme nous allons le voir.

Avec la méthode some

Les itérations s’arrêtent dès qu’une condition est remplie :


const breakable = [1, 5, 10, 15];

function breakOnEven(element) {
    const isEven = element % 2 === 0;
    if (isEven) console.log(element + ' is even, break');
    else console.log(element + ' is odd');
    return isEven;
}
breakable.some(breakOnEven);

Some, to break loop

continue dans un forEach

Il n’y a pas non plus d’instruction continue comme dans une boucle classique for ou for..of.

Mais avec un return dans la fonction callback, il est possible d’obtenir un comportement similaire.
Le code restant du callback n’est pas exécuté, et on passe à l’itération suivante :


const continueable = [1, 5, 10, 15];

function continueOnEven(element) {
    const isEven = element % 2 === 0;
    if (isEven) return;

    console.log(element + ' is odd');
}
continueable.forEach(continueOnEven);

foreach : return to continue

Objets proches de Array

D’autres structures ressemblent à des arrays mais n’en sont pas, notamment les structures proposées par les Browser APIs (c’est à dire par le navigateur, et non par le moteur JS), comme NodeList et HTMLCollection.

HTMLCollection

HTMLCollection est retournée par la méthode de Browser API getElementsByClassName.

HTMLCollection est une liste dynamique (‘live’) d’éléments HTML correspondant au sélecteur passé en argument. Elle est automatiquement mise à jour lorsque le document est modifié.
Une HTMLCollection n’est pas un véritable Array, et ne peut pas être itérée directement avec la méthode forEach, même avec les browsers récents.

Voyons comment itérer sur une HTMLCollection par d’autres moyens.

Alternatives

Dans les exemples  suivants, on utilisera le code HTML suivant :


<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>

et on obtient la HTMLCollection contenant les 3 tags ainsi :


const htmlCollection = document.getElementsByClassName('element');

Bon, nous allons pouvoir appliquer sur cette collection une première alternative pour itérer : en passant par le prototype de Array.

Array.prototype.forEach.call

Cette approche a le mérite de fonctionner dans tous les navigateurs :


Array.prototype.forEach.call(htmlCollection, function(element) {
    console.log(element);
});

ou variante un peu plus concise :


[].forEach.call(htmlCollection, function(element) {
    console.log(element);
});

Boucle for classique

Le grand classique, pas très lisible, mais qui fonctionne également dans tous les navigateurs :


for (let i = 0; i < htmlCollection.length; i++) {
    console.log(htmlCollection[i]);
}

Conversion en Array, puis forEach

On peut aussi d’abord convertir la HTMLCollection en Array, surtout si on a besoin de la réutiliser sous cette forme, et la parcourir ensuite.

Il y a trois façons de la convertir en tableau :

Array.prototype.slice.call

Fonctionne dans tous les navigateurs (sauf Internet Explorer < 11) :


const collectionAsArray = Array.prototype.slice.call(htmlCollection);

collectionAsArray.forEach(function(element) {
    console.log(element);
});

ou un peu mieux :


const elementsAsArray = [].slice.call(htmlCollection);
// etc...

Spread opearator

L’opérateur spread est plus concis que slice, mais il lui manque le support d’Internet Explorer, même en version 11 :


const collectionAsArray = [...htmlCollection];
collectionAsArray.forEach(element => console.log(element));

Array.from

Là aussi, il manque le support d’Internet Explorer, même en version 11 :


const collectionAsArray = Array.from(htmlCollection); 
collectionAsArray.forEach(element => console.log(element));

Boucle for..of

Le code idéal, car concis et lisible ?

Par contre, apparue avec la version ES6 de JavaScript, la boucle for..of n’est supportée que par les navigateurs evergreen.
Et encore, Edge ne l’a pas implémentée sur la HTMLCollection, mais c’est prévu pour l’automne 2018 :


for (const element of htmlCollection) {
    console.log(element);
}

C’est beau, quand même…

Conclusion

Il y a beaucoup (trop !) d’approches possibles en JavaScript pour parcourir les une HTMLCollection, alors si vous ne deviez retenir qu’une chose, la voici :

  • Pour supporter tous les navigateurs :
    Boucle for classique, ou [].forEach.call
  • Pour être concis et lisible, en ciblant uniquement les navigateurs evergreen (et en misant sur la mise à jour de Edge) :
    Boucle for..of

NodeList

C’est une autre structure ressemblant à un tableau JavaScript, sans un être un.

NodeList est retournée par les méthodes de Browser API querySelectorAll et childNodes

NodeList est une liste statique (c’est à dire pas ‘live’) d’éléments correspondant au sélecteur passé en argument. Ce sont les tags existants au moment de la création de la page.

Iteration de NodeList avec forEach

Bien que n’étant pas un véritable Array, une NodeList peut être itérée directement avec la méthode forEach, avec les navigateurs récents.

Avec le code HTML suivant :


<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>

on obtient la NodeList et on la parcourt ainsi :


const nodeList = document.querySelectorAll('.element');
nodeList.forEach(element => console.log(element));
Aucun Internet Explorer ne gère forEach sur NodeList (même pas IE 11).

Il faut alors passer par un appel sur le prototype de Array :


Array.prototype.forEach.call(nodeList, function(element) {
    console.log(element);
});

ou variante un peu plus concise :


[].forEach.call(nodeList, function(element) {
    console.log(element);
});

ou passer par une boucle for classique.

Méthodes plus adaptées

Le but de forEach est uniquement d’obtenir chaque élément de la collection, pas de créer une nouvelle collection résultant d’une transformation, car pour cela il existe des méthodes comme filter, map, etc…

forEach ou méthode filter ?

Constituer un autre tableau à partir de forEach peut être tentant :


const toFilter = [1, 5, 10, 20];
const even = [];

function filterEven(element) {
    const isEven = element % 2 === 0;

    if (isEven) {
        even.push(element);
    }
}
// DON'T DO THAT!
toFilter.forEach(filterEven);

Mais il existe une méthode bien mieux adaptée, filter :


const toFilter = [1, 5, 10, 20];

function filterEven(element) {
    const isEven = element % 2 === 0;
    return isEven;
}

const even = toFilter.filter(filterEven);

console.log(even);

result of filter

Avantages

Testable

L’implémentation du callback ne repose pas sur donné extérieure (le tableau destination), ce qui le rend plus facilement testable.

Lisible

L’appel à filter indique tout de suite l’intention de filtrer pour créer et remplir un tableau.

Assez compatible

Fonctionne avec Internet Explorer 11.

forEach ou méthode map ?

De même la tentation peut être grande de transformer les éléments avec forEach, et de les insérer dans un nouveau tableau, mais il existe la méthode map spécialement conçue pour ça :


const raw = [1, 5, 10, 20];

function toObject(element) {
    return { points: element };
}

const objects = raw.map(toObject);

console.log(objects);

Ce qui produit un nouveau tableau, contenant maintenant des objets :

Map result

Avantages

Les mêmes que pour filter, plus la capacité à utiliser les promises et async / await.

De plus, les méthodes telles que filter et map sont chaînables et adaptée à la programmation fonctionnelle. Alors que malgré les apparences, forEach ne l’est pas, et reste orientée programmation impérative.

Chainer plusieurs méthodes comme filter et map permet la « séparation des taches » : filtrer et mapper sont deux taches bien différentes. Elles n’ont pas lieu d’être mêlées dans une seule fonction, comme dans le cas d’un forEach.

Par contre filter, map doivent être utilisées uniquement pour produire un résultat.
Et non pas pour causer des modifications sur l’environnement, alors que forEach le peut.

Boucles alternatives

Bonus : Téléchargez le Guide Facile des Boucles pour trouver rapidement la boucle la mieux adaptée selon les cas et les navigateurs

forEach ou for..of ?

for..of est une nouvelle boucle apparue en JavaScript ES6, assez élégante :


for (const el of [1, 10, 20]) {
    console.log(el);
}

Et il faut bien constater que forEach ne présente pas d’avantage majeur par rapport à for-of.
On peut mentionner quand même quelques point positifs :

Compatibilité

forEach, apparue avec JavaScript ES5, est compatible avec Internet Explorer 11.

Index

Léger avantage pour l’accès direct à l’index, avec forEach l’index arrive directement dans le second paramètre du callack.

Alors qu’avec for..of, il faut itérer sur array.entries() ou .keys() :


for (const index of [1, 10, 20].keys()) {
    console.log(index);
}

for (const [index, value] of [1, 10, 20].entries()) {
    console.log(index + ': ' + value);
}

Et si lors de l’écriture du code, on avait pas prévu de gérer les index, et que le callback est une fonction séparée, pour pouvoir utiliser les index il faut aussi refactoriser l’en-tête de boucle :

Avant utilisation des index


for (const element of [1, 10, 20]) {
    process(element);
}

function process(element) {
    // ...
}

Après utilisation des index


for (const [key, element] of [1, 10, 20].entries() {
    process(element, key);
}

function process(element, key) {
    // ...
    // now we can use key
}

Alors qu’avec forEach, c’est très simple :

Avant utilisation des index


[1, 10, 20].forEach(process);

function process(element) {
    // ...
}

Après utilisation des index


[1, 10, 20].forEach(process); // not touched!

function process(element, key) {
    // ...
    // just use key!
    console.log(key + ': '+ element);
}

Elégance

Bien que for..of soit déjà très lisible et court, forEach est peut être encore légèrement mieux ?

Comparer :


for (const e of [1, 10, 20]) {
    console.log(e);
}

et :


[1, 10, 20].forEach(e => console.log(e));

forEach ou for classique ?

La bonne vieille boucle for fonctionnera dans tous les cas. Au prix d’une syntaxe un peu lourde :


const array = [1, 10, 20];

for (let i=0; i<array.length; i++) {
    console.log(array[i]);
}

forEach ou for ?

Lisibilité, fiabilité

  • forEach est beaucoup plus lisible :
    • moins verbeux
    • pas de variable temporaire
  • Pas de risque de typo sur la condition de boucle (avec for, si on se trompe sur l’index de départ, l’infériorité stricte ou non-stricte… on tombe dans l’erreur courante « off-by-one »).

Comparer :


const a = [1, 10, 20];

for (let i=0; i<a.length; i++) {
    console.log(a[i]);
}

et :


const a = [1, 10, 20];
a.forEach(e => console.log(e));

Performance

Oui, sur le papier, la performance de forEach est inférieure à celle de for, mais cela ne fera pas de différence dans la plupart des cas : utiliser systématiquement un for classique serait de l’optimisation prématurée.

Appel d’une fonction avec callback

Dans le cas où l’on doit appeler une fonction tierce prenant un callback en argument, forEach fonctionne facilement :


const a = [1, 10, 20];

a.forEach(function(element) {
    // simulate asynchronous action with an element
    setTimeout(function() {
       console.log(element);
   }, 3000);
});

foreach et fonction tierce avec callback

Alors qu’une boucle for, si elle gère sa variable avec var, ne peut pas fonctionner :


for (var i = 0; i < a.length; i++) {
    setTimeout(function() {
        console.log(a[i]);
        // does not work
    }, 3000);
}

for avec var et callback

Ci-dessus, l’index i est déjà à 3 quand le 1er accès a[i] se fait.
Cela oblige à utiliser let à la place de var, donc faire une croix sur la compatibilité IE 11 (ou à utiliser encore d’autres subterfuges).

for..in

Disons le tout  net, la boucle for..in est conçue pour itérer sur les propriétés d’un objet (ou les caractères d’une string). Et non pas sur les valeurs d’un tableau (c’est tout à fait officiel, voir MDN).

for..in  va renvoyer non seulement chaque élément du tableau, mais aussi chaque propriété énumérable, ainsi que toute propriété héritée.De plus, l’ordre des itérations n’est pas garanti.

Donc pour un tableau, on oublie for..in !

Il est possible d’itérer avec forEach sur un objet JavaScript, notamment JSON, en passant par Object.keys. Voir un exemple.

Conclusion : forEach et les autres boucles

Nous avons vu les forces et faiblesses de forEach, for..of et for, qui sont toutes des alternatives utilisables pour itérer sur un tableau. Par contre for..in est à éviter sur un tableau.

foreach et les autre boucles

Autres exemples pour la route

Détecter le dernier élément

Pour tester si un élément est le dernier du tableau, il suffit de passer l’index et le tableau en arguments au callback :


const array =['1st element', '2nd element', 'last element'];

array.forEach((element, index, currentArray) => {

    if (index === currentArray.length - 1) {
        console.log(element);
    }
});

forEach sur une table HTML

Pour retrouver les cellules de cette table :


<table class="iterate-me">
    <tr>
        <th>col 1</th>
        <th>col 2</th>
    </tr>
    <tr>
        <td>cell 1</td>
        <td>cell 2</td>
    </tr>
    <tr>
        <td>cell 3</td>
        <td>cell 4</td>
    </tr>
</table>

Une possibilité avec forEach est de parcourir chaque tr dans la table, puis chaque td dans un tr :


const table = document.querySelector('.iterate-me');
const rows = table.querySelectorAll('tr');

rows.forEach(row => {
    const cells = row.querySelectorAll('td');

    cells.forEach(cell => console.log(cell));
});

forEach pour parcourir une table HTML

Questions fréquentes

Comment…

Comment faire un break
Il n’existe pas d’instruction break dans forEach, par contre il y a d’autres solutions
Comment faire un continue
Il suffit de remplacer le continue par un return
Comment obtenir l’index
Le 2ème argument du callback permet d’accéder à l’index
Ajouter à un Array 
En fait, ce n’est pas la vocation de forEach de produire une autre collection. Pour cela, il existe les méthodes comme map et filter
Array vers JSON
Plutôt qu’utiliser forEach, il y a JSON.stringify qui permet de convertir le tableau en une string JSON :


const array = ['High', 5];
let json = JSON.stringify(array);
console.log(`Produced type ${typeof json}, and value ${json}`);

Produced type string, and value ["High",5]

Erreurs

forEach is not a function

C’est ce qui arrive quand on applique la méthode forEach à un objet ressemblant à un Array, mais qui n’en est pas un, et qui ne supporte pas forEach. Comme par ex. HTMLCollection. Voir plus haut comment parcourir cette structure.

undefined is not a function

L’argument passé doit être une référence de fonction. Et non une invocation de fonction comme ci-dessous :


const array = [4, 5];

array.forEach(someFunction());

function someFunction() {
    // do whatever
    console.log('Found an element');
}

erreur avec foreach : undefined is not a function

Donc, pas de parenthèses après la fonction en argument :


const array = [4, 5];

array.forEach(someFunction);

function someFunction() {
    // do whatever
    console.log('Found an element');
}

Usage correct de la fonction pour forEach

Reference Rapide forEach Téléchargez la Référence Rapide forEach
+ en bonus : le Guide Facile des Boucles


Et maintenant, allez-vous utiliser forEach, ou plutôt une autre boucle ? Partagez votre avis dans les commentaires !

Laisser un commentaire