Chapitre 518 minDébutant
Tableaux JavaScript : map, filter, reduce, find et sort
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- créer un tableau, y ajouter et en retirer des éléments ;
- chercher un élément avec
find,someouincludes; - transformer, filtrer et résumer un tableau avec
map,filteretreduce; - trier correctement des nombres et des chaînes ;
- distinguer les méthodes qui modifient le tableau de celles qui en créent un nouveau.
4 exercices corrigés1 schéma
Un tableau JavaScript (Array) est une liste ordonnée de valeurs, numérotées à partir de 0, dont la taille varie librement. Ses méthodes map, filter et reduce transforment, filtrent et résument une liste en une ligne, sans boucle écrite à la main. Ce chapitre présente la création d'un tableau, l'ajout et le retrait d'éléments, la recherche, les trois méthodes clés, le tri, et la différence entre les méthodes qui modifient le tableau et celles qui en créent un nouveau. Les algorithmes de base sur les listes (recherche, maximum, comptage) sont expliqués dans le chapitre Tableaux et listes du cours d'algorithmique.
Tous les exemples utilisent le tableau pieces du chapitre 3.
Créer un tableau, lire et modifier un élément
const refs = ['SSD-512', 'RAM-16', 'CHG-65'];
refs[0] // 'SSD-512' : le premier élément est à l'indice 0
refs.length // 3
refs[refs.length - 1] // 'CHG-65' : le dernier
refs.at(-1) // 'CHG-65' : at accepte un indice négatif, compté depuis la fin
refs[10] // undefined : pas d'erreur, mais pas de valeur
refs[1] = 'RAM-32'; // modification : ['SSD-512', 'RAM-32', 'CHG-65']
Un tableau peut contenir des valeurs de tous types, y compris des objets et d'autres tableaux. Dans une application, tu manipuleras surtout des tableaux d'objets : une liste de pièces, de clients, de commandes, comme les lignes d'une table SQL.
Ajouter et retirer des éléments
| Méthode | Effet | Renvoie | Exemple sur ['SSD-512', 'RAM-16'] |
|---|---|---|---|
push(x) | Ajoute à la fin | La nouvelle longueur | push('CHG-65') renvoie 3 |
pop() | Retire le dernier | L'élément retiré | renvoie 'RAM-16' |
unshift(x) | Ajoute au début | La nouvelle longueur | unshift('PAT-01') renvoie 3 |
shift() | Retire le premier | L'élément retiré | renvoie 'SSD-512' |
splice(i, n) | Retire n éléments à partir de l'indice i | Un tableau des éléments retirés | voir ci-dessous |
const liste = ['SSD-512', 'RAM-16', 'CHG-65', 'ECR-156'];
liste.splice(1, 2); // renvoie ['RAM-16', 'CHG-65']
console.log(liste); // ['SSD-512', 'ECR-156']
liste.splice(1, 0, 'NEW-01'); // insère sans rien retirer
console.log(liste); // ['SSD-512', 'NEW-01', 'ECR-156']
Ces cinq méthodes modifient le tableau sur lequel tu les appelles. slice(debut, fin), au nom presque identique, ne modifie rien : elle renvoie une copie d'une partie du tableau (['a', 'b', 'c', 'd'].slice(1, 3) donne ['b', 'c']).
Chercher dans un tableau : includes, find, some
| Méthode | Question posée | Renvoie |
|---|---|---|
includes(valeur) | La valeur est-elle présente ? | true ou false |
indexOf(valeur) | À quel indice ? | L'indice, ou -1 |
find(test) | Quel est le premier élément qui passe le test ? | L'élément, ou undefined |
findIndex(test) | À quel indice est-il ? | L'indice, ou -1 |
findLast(test) | Quel est le dernier élément qui passe le test ? | L'élément, ou undefined |
some(test) | Au moins un élément passe-t-il le test ? | true ou false |
every(test) | Tous les éléments passent-ils le test ? | true ou false |
includes et indexOf comparent avec === : ils conviennent aux valeurs simples (chaînes, nombres), pas aux objets. Pour un tableau d'objets, passe un test, une fonction qui reçoit un élément et renvoie vrai ou faux :
const ecran = pieces.find((p) => p.ref === 'ECR-156');
console.log(ecran.nom); // 'Dalle 15,6 pouces'
pieces.find((p) => p.ref === 'XXX-00'); // undefined
pieces.find((p) => p.prix > 40).ref; // 'SSD-512' (le premier)
pieces.findIndex((p) => p.ref === 'ECR-156'); // 3
pieces.some((p) => p.quantite === 0); // true : au moins une rupture
pieces.every((p) => p.quantite > 0); // false
map : transformer chaque élément
map applique une fonction à chaque élément et renvoie un nouveau tableau de même longueur, rempli des résultats :
pieces.map((p) => p.ref);
// ['SSD-512', 'RAM-16', 'CHG-65', 'ECR-156', 'PAT-01']
pieces.map((p) => p.quantite * p.prix);
// [514.8, 115.5, 0, 178, 162.5]
pieces.map((p) => ({ ref: p.ref, valeur: p.quantite * p.prix }));
// [{ ref: 'SSD-512', valeur: 514.8 }, { ref: 'RAM-16', valeur: 115.5 }, …]
La fonction passée à map doit renvoyer une valeur. Avec des accolades sans return, tu obtiens un tableau de undefined :
[12, 8, 15].map((n) => { n + 1; }); // [undefined, undefined, undefined]
[12, 8, 15].map((n) => n + 1); // [13, 9, 16]
filter : garder certains éléments
filter renvoie un nouveau tableau qui ne contient que les éléments pour lesquels le test renvoie vrai. Il peut être plus court que l'original, voire vide :
const enAlerte = pieces.filter((p) => p.quantite <= p.seuil);
enAlerte.length; // 2
enAlerte.map((p) => p.ref); // ['RAM-16', 'CHG-65']
pieces.filter((p) => p.prix < 30).length; // 2 : CHG-65 et PAT-01
reduce : résumer un tableau en une valeur
reduce parcourt le tableau en faisant évoluer un accumulateur, et renvoie sa valeur finale. Elle prend deux arguments : une fonction (accumulateur, element) => nouvelAccumulateur, et la valeur de départ de l'accumulateur.
const unites = pieces.reduce((total, p) => total + p.quantite, 0);
console.log(unites); // 42
const valeur = pieces.reduce((total, p) => total + p.quantite * p.prix, 0);
console.log(valeur); // 970.8
Pour la valeur du stock, l'accumulateur total part de 0 et reçoit, à chaque pièce, sa quantité multipliée par son prix :
| Tour | Pièce | total reçu | Calcul | total renvoyé |
|---|---|---|---|---|
| 1 | SSD-512 | 0 | 0 + 12 × 42,9 | 514,8 |
| 2 | RAM-16 | 514,8 | 514,8 + 3 × 38,5 | 630,3 |
| 3 | CHG-65 | 630,3 | 630,3 + 0 × 24 | 630,3 |
| 4 | ECR-156 | 630,3 | 630,3 + 2 × 89 | 808,3 |
| 5 | PAT-01 | 808,3 | 808,3 + 25 × 6,5 | 970,8 |
L'accumulateur n'est pas forcément un nombre. Pour trouver la pièce la plus chère, il contient la meilleure pièce vue jusque-là :
const plusChere = pieces.reduce((max, p) => (p.prix > max.prix ? p : max));
console.log(plusChere.ref); // 'ECR-156'
Sans valeur de départ, reduce prend le premier élément comme accumulateur. C'est pratique pour un maximum, mais sur un tableau vide, il plante : TypeError: Reduce of empty array with no initial value. Pour une somme, donne toujours la valeur de départ 0 : la somme d'un tableau vide vaut alors 0.
Enchaîner filter, map et reduce
Chaque méthode renvoie un tableau, sur lequel on peut appeler la suivante. Le code se lit comme une phrase : « parmi les pièces, garde celles en alerte, puis fabrique pour chacune une ligne de commande » :
const bonDeCommande = pieces
.filter((p) => p.quantite <= p.seuil)
.map((p) => `${p.ref} : commander ${p.seuil * 2 - p.quantite}`);
console.log(bonDeCommande);
// ['RAM-16 : commander 5', 'CHG-65 : commander 4']
La règle de réassort de Réparinfo : remonter le stock au double du seuil. RAM-16 (seuil 4, quantité 3) : 8 − 3 = 5 ; CHG-65 (seuil 2, quantité 0) : 4 − 0 = 4.
Autre exemple : la valeur du stock hors consommables.
pieces
.filter((p) => p.categorie !== 'Consommable')
.reduce((total, p) => total + p.quantite * p.prix, 0); // 808.3
Trier un tableau : sort et toSorted
sort trie le tableau sur place (il le modifie) et, sans argument, compare les éléments comme des chaînes :
[10, 9, 1, 100].sort(); // [1, 10, 100, 9] : ordre alphabétique !
[10, 9, 1, 100].sort((a, b) => a - b); // [1, 9, 10, 100] : ordre croissant
[10, 9, 1, 100].sort((a, b) => b - a); // [100, 10, 9, 1] : ordre décroissant
La fonction de comparaison reçoit deux éléments a et b et renvoie un nombre négatif si a doit venir avant b, positif si a doit venir après, 0 s'ils sont équivalents. a - b fait exactement cela pour des nombres. Pour des chaînes en français, utilise localeCompare, qui range correctement les accents et les majuscules :
['écran', 'Zèbre', 'alim', 'Écran'].sort();
// ['Zèbre', 'alim', 'Écran', 'écran'] : ordre des codes Unicode, inutilisable
['écran', 'Zèbre', 'alim', 'Écran'].toSorted((a, b) => a.localeCompare(b, 'fr'));
// ['alim', 'écran', 'Écran', 'Zèbre']
Depuis ECMAScript 2023, toSorted fait la même chose que sort mais renvoie une copie triée et laisse l'original intact. Utilise-la dès que le tableau d'origine sert ailleurs, par exemple l'état de ton application :
const parPrixDecroissant = pieces.toSorted((a, b) => b.prix - a.prix);
parPrixDecroissant.map((p) => p.ref);
// ['ECR-156', 'SSD-512', 'RAM-16', 'CHG-65', 'PAT-01']
const parNom = pieces.toSorted((a, b) => a.nom.localeCompare(b.nom, 'fr'));
parNom.map((p) => p.ref);
// ['RAM-16', 'CHG-65', 'ECR-156', 'PAT-01', 'SSD-512']
De la même façon, toReversed() et with(indice, valeur) sont les versions sans modification de reverse() et de tableau[indice] = valeur.
Modifier ou créer : le tableau récapitulatif
Savoir si une méthode modifie le tableau évite une catégorie entière de bugs, en particulier dans React, qui ne voit pas un tableau modifié sur place (l'état dans React).
| Modifient le tableau (mutation) | Renvoient un nouveau tableau ou une valeur |
|---|---|
push, pop, shift, unshift | concat, slice, [...a, x] |
splice | toSpliced |
sort, reverse | toSorted, toReversed |
tableau[i] = x | with(i, x) |
fill, copyWithin | map, filter, reduce, flat, flatMap |
forEach ou for…of ?
forEach appelle une fonction pour chaque élément, sans rien renvoyer. Elle remplace une boucle for…of, avec une limite : impossible d'en sortir avec break, et un return dans le callback ne fait que passer à l'élément suivant.
pieces.forEach((p, i) => console.log(`${i + 1}. ${p.ref}`));
Règle simple : map si tu fabriques un tableau, filter si tu sélectionnes, reduce si tu résumes, find ou some si tu cherches, et for…of pour tout le reste (afficher, enregistrer), surtout s'il faut pouvoir s'arrêter.
Copier et décomposer : spread et destructuring
L'opérateur de décomposition ... (spread) étale les éléments d'un tableau. Il sert à copier, à fusionner, à ajouter sans modifier :
const a = ['SSD-512', 'RAM-16'];
const b = ['CHG-65'];
const tous = [...a, ...b]; // ['SSD-512', 'RAM-16', 'CHG-65']
const copie = [...a]; // une copie, indépendante de a
const avecNouvelle = [...a, 'X']; // ajout sans toucher à a
Math.max(...[3, 9, 2]); // 9 : les éléments deviennent des arguments
La déstructuration (destructuring) fait l'inverse : elle range les éléments d'un tableau dans des variables, dans l'ordre.
const [premiere, deuxieme, ...autres] = pieces.map((p) => p.ref);
// premiere = 'SSD-512', deuxieme = 'RAM-16', autres = ['CHG-65', 'ECR-156', 'PAT-01']
let x = 'a';
let y = 'b';
[x, y] = [y, x]; // échange : x = 'b', y = 'a'
Quelques méthodes utiles en plus
['SSD-512', 'RAM-16'].join(', '); // 'SSD-512, RAM-16'
Array.from({ length: 3 }, (_, i) => i + 1); // [1, 2, 3]
Array.from('RAM'); // ['R', 'A', 'M']
[[1, 2], [3]].flat(); // [1, 2, 3]
pieces.flatMap((p) => (p.quantite === 0 ? [] : [p.ref]));
// ['SSD-512', 'RAM-16', 'ECR-156', 'PAT-01'] : map et filtre en un passage
Object.groupBy (ECMAScript 2024) range les éléments dans un objet, selon une clé calculée :
const groupes = Object.groupBy(pieces, (p) => (p.quantite <= p.seuil ? 'alerte' : 'ok'));
groupes.alerte.map((p) => p.ref); // ['RAM-16', 'CHG-65']
groupes.ok.map((p) => p.ref); // ['SSD-512', 'ECR-156', 'PAT-01']
Fil rouge : les règles du stock en une ligne
Avec les méthodes de ce chapitre, les règles de Gestock deviennent plus courtes et plus lisibles. Voici celles du futur fichier stock.js :
function estEnAlerte(piece) {
return piece.quantite <= piece.seuil;
}
function valeurStock(pieces) {
return pieces.reduce((total, p) => total + p.quantite * p.prix, 0);
}
function filtrerPieces(pieces, texte = '', alertesSeules = false) {
const recherche = texte.trim().toLowerCase();
return pieces.filter((p) => {
const correspond =
p.nom.toLowerCase().includes(recherche) || p.ref.toLowerCase().includes(recherche);
return correspond && (!alertesSeules || estEnAlerte(p));
});
}
filtrerPieces(pieces, 'ssd').map((p) => p.ref); // ['SSD-512']
filtrerPieces(pieces, '', true).map((p) => p.ref); // ['RAM-16', 'CHG-65']
filtrerPieces(pieces, 'ram', true).map((p) => p.ref); // ['RAM-16']
filtrerPieces garde une pièce si sa référence ou son nom contient le texte cherché (sans tenir compte des majuscules), et, quand alertesSeules vaut true, seulement si elle est en alerte. Une recherche vide ('') garde tout : toute chaîne contient la chaîne vide. Cette fonction sera branchée sur le champ de recherche au chapitre 8.
Exercices
Exercice 1 sur 4 Prédire les résultats Réussi À revoir
Donne le résultat de chaque expression avec le tableau pieces.
pieces.map((p) => p.quantite)pieces.filter((p) => p.quantite > 10).map((p) => p.ref)pieces.find((p) => p.categorie === 'Écran').prixpieces.some((p) => p.prix > 100)pieces.reduce((t, p) => t + p.seuil, 0)[5, 25, 100, 3].sort()
Voir la réponseMasquer la réponse de l'exercice 1
[12, 3, 0, 2, 25]['SSD-512', 'PAT-01']89false: la pièce la plus chère coûte 89 €.22: 5 + 4 + 2 + 1 + 10.[100, 25, 3, 5]: sans fonction de comparaison,sortcompare des chaînes, et'100'vient avant'25', qui vient avant'3', qui vient avant'5'. Écris.sort((a, b) => a - b)pour obtenir[3, 5, 25, 100].
Tu avais trouvé ?
Exercice 2 sur 4 Statistiques du stock Réussi À revoir
Écris, chacune en une instruction, les expressions qui donnent :
- les noms des pièces en majuscules, sauf celles en rupture ;
- le prix moyen des pièces (sans tenir compte des quantités), arrondi à deux décimales ;
- le nombre de pièces des catégories Stockage ou Mémoire.
Voir la réponseMasquer la réponse de l'exercice 2
// 1.
pieces.filter((p) => p.quantite > 0).map((p) => p.nom.toUpperCase());
// ['SSD NVME 512 GO', 'BARRETTE DDR4 16 GO', 'DALLE 15,6 POUCES', 'PÂTE THERMIQUE']
// 2.
(pieces.reduce((s, p) => s + p.prix, 0) / pieces.length).toFixed(2);
// '40.18' : (42,9 + 38,5 + 24 + 89 + 6,5) / 5 = 200,9 / 5
// 3.
pieces.filter((p) => p.categorie === 'Stockage' || p.categorie === 'Mémoire').length;
// 2
Tu avais trouvé ?
Exercice 3 sur 4 Totaliser les ventes par référence Réussi À revoir
Les sorties de stock de la journée sont enregistrées ainsi :
const sorties = [
{ ref: 'SSD-512', qte: 2 },
{ ref: 'RAM-16', qte: 1 },
{ ref: 'SSD-512', qte: 1 },
{ ref: 'PAT-01', qte: 3 },
];
Avec reduce, construis un objet qui donne la quantité totale sortie pour chaque référence.
Voir la réponseMasquer la réponse de l'exercice 3
const parRef = sorties.reduce((acc, s) => {
acc[s.ref] = (acc[s.ref] ?? 0) + s.qte;
return acc;
}, {});
// { 'SSD-512': 3, 'RAM-16': 1, 'PAT-01': 3 }
L'accumulateur est un objet vide au départ. Pour chaque sortie, on ajoute sa quantité au total de sa référence (?? 0 donne 0 la première fois). La fonction doit renvoyer l'accumulateur à chaque tour.
Tu avais trouvé ?
Exercice 4 sur 4 Sans modifier l'original Réussi À revoir
Ce code doit afficher les pièces de la moins chère à la plus chère, sans changer l'ordre du stock affiché ailleurs dans l'application. Pourquoi est-il faux ? Corrige-le.
const parPrix = pieces.sort((a, b) => a.prix - b.prix);
Voir la réponseMasquer la réponse de l'exercice 4
sort trie sur place : pieces lui-même est réordonné, et parPrix désigne le même tableau. Tout le reste de l'application voit le stock trié par prix.
const parPrix = pieces.toSorted((a, b) => a.prix - b.prix);
toSorted renvoie une copie triée. Sur un navigateur très ancien qui ne la connaîtrait pas, [...pieces].sort(…) fait la même chose.
Tu avais trouvé ?