Cours JavaScript et TypeScript · Module 5: JavaScript avancé

Les méthodes avancées des tableaux : map, filter, reduce

8 min de lecture
Dans cette leçon7

Le vétérinaire du parc demande un rapport : les noms de tous les prédateurs, leur niveau de danger total et l'âge moyen du troupeau. Avec une simple boucle for, c'est faisable, mais chaque réponse demande plusieurs lignes de compteurs, de conditions et de tableaux temporaires. Maintenant que tu connais les méthodes de base des tableaux, il est temps de découvrir les trois plus importantes méthodes d'ordre supérieur (higher-order methods), c'est-à-dire celles qui prennent une fonction en argument. Elles sont le fondement de la programmation fonctionnelle en JavaScript et extrêmement utiles pour travailler avec les données des dinosaures.

map() - transformer les données

La méthode map() crée un nouveau tableau en exécutant une fonction sur chaque élément du tableau d'origine. Elle ne modifie pas le tableau d'origine. Ce que renvoie la fonction va dans le résultat :

1const dinosaurs = [
2  { name: "Blue", age: 4, species: "Velociraptor" },
3  { name: "Rex", age: 12, species: "T-Rex" },
4  { name: "Bronto", age: 8, species: "Brachiosaurus" }
5];
6
7// Extraire seulement les noms
8const names = dinosaurs.map(dino => dino.name);
9console.log(names); // ["Blue", "Rex", "Bronto"]
10
11// Transformer vers un nouveau format
12const reports = dinosaurs.map(dino => ({
13  label: dino.name + " (" + dino.species + ")",
14  ageInMonths: dino.age * 12
15}));
16console.log(reports);
17// [{ label: "Blue (Velociraptor)", ageInMonths: 48 }, ...]

Le résultat a toujours autant d'éléments que l'original : trois dinosaures en entrée, trois rapports en sortie. Les parenthèses autour du littéral d'objet dans le second exemple sont indispensables, car sans elles JavaScript prendrait les accolades pour le corps de la fonction fléchée.

filter() - filtrer les données

La méthode filter() crée un nouveau tableau qui ne contient que les éléments remplissant la condition donnée (la fonction renvoie true). Elle fonctionne comme le portillon d'un enclos :

1const dinosaurs = [
2  { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
3  { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
4  { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
5  { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 }
6];
7
8// Filtrer les prédateurs
9const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
10console.log(carnivores.length); // 2
11
12// Les dinosaures au niveau de danger élevé
13const dangerous = dinosaurs.filter(dino => dino.dangerLevel >= 8);
14console.log(dangerous.map(d => d.name)); // ["Blue", "Rex"]

Seuls les dinosaures pour lesquels la fonction a renvoyé true passent le portillon. Ils ne sont pas copiés pour autant - ce sont les mêmes objets que dans l'original, rassemblés dans un nouveau tableau. Si personne ne remplit la condition, tu obtiens un tableau vide, et non undefined.

reduce() - agréger les données

La méthode reduce() réduit un tableau à une valeur unique, en traitant les éléments un par un avec un accumulateur. L'accumulateur est le carnet du laborantin, et le second argument de reduce() est son état initial :

1const dinosaurs = [
2  { name: "Blue", age: 4 },
3  { name: "Rex", age: 12 },
4  { name: "Bronto", age: 8 },
5  { name: "Triki", age: 6 }
6];
7
8// Somme de tous les âges
9const totalAge = dinosaurs.reduce((sum, dino) => sum + dino.age, 0);
10console.log(totalAge); // 30
11
12// Âge moyen
13const avgAge = totalAge / dinosaurs.length;
14console.log(avgAge); // 7.5

La somme se déroule ainsi : 0 + 4, puis + 12, + 8 et + 6, ce qui donne 30. Indique toujours la valeur de départ : sans elle, reduce() prendra le premier élément comme accumulateur, c'est-à-dire ici tout l'objet Blue, et au lieu d'un nombre tu obtiendras un texte collé. Sur un tableau vide sans valeur de départ, un TypeError sera levé.

L'accumulateur n'est pas forcément un nombre. Ce peut être un objet dans lequel on range les dinosaures selon leur régime - c'est ainsi que fonctionne le regroupement :

1const dinosaurs = [
2  { name: "Blue", age: 4, diet: "carnivore" },
3  { name: "Rex", age: 12, diet: "carnivore" },
4  { name: "Bronto", age: 8, diet: "herbivore" },
5  { name: "Triki", age: 6 } // aucun régime enregistré
6];
7
8// Regrouper les dinosaures par régime
9const grouped = dinosaurs.reduce((groups, dino) => {
10  const key = dino.diet || "unknown";
11  if (!groups[key]) groups[key] = [];
12  groups[key].push(dino.name);
13  return groups;
14}, {});
15
16console.log(grouped);
17// { carnivore: ["Blue", "Rex"], herbivore: ["Bronto"], unknown: ["Triki"] }

L'objet vide passé comme valeur de départ devient le catalogue des groupes, et Triki, sans régime enregistré, atterrit dans le groupe unknown grâce à l'opérateur ||. Depuis ES2024, Object.groupBy() fait la même chose plus brièvement, tu le découvriras dans la leçon sur les nouveautés de JavaScript.

Combiner les méthodes en pipeline

La vraie puissance de ces méthodes se révèle quand on les enchaîne (chaining). Chaque méthode reçoit le résultat de la précédente, comme un échantillon transmis d'un poste du laboratoire à l'autre :

1const dinosaurs = [
2  { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
3  { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
4  { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
5  { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 }
6];
7
8// Pipeline : filtrer les prédateurs -> extraire les noms -> trier
9const dangerousNames = dinosaurs
10  .filter(dino => dino.diet === "carnivore")
11  .map(dino => dino.name)
12  .sort();
13
14console.log(dangerousNames); // ["Blue", "Rex"]
15
16// Niveau de danger total des prédateurs
17const totalDanger = dinosaurs
18  .filter(dino => dino.diet === "carnivore")
19  .reduce((sum, dino) => sum + dino.dangerLevel, 0);
20
21console.log(totalDanger); // 18

L'ordre des maillons compte : on filtre d'abord, pour que les étapes suivantes travaillent sur un tableau plus petit. Méfie-toi de sort(), car contrairement au reste de la chaîne, il modifie le tableau - ici, il est sans danger, car il trie le résultat tout frais de map(). Sans fonction de comparaison, il trie aussi comme du texte, donc les nombres 9, 10 et 1 seraient rangés 1, 10, 9.

Chercher et vérifier

Toutes les questions ne demandent pas un nouveau tableau. Parfois, tu veux trouver un seul dinosaure, ou seulement vérifier une condition - c'est le rôle des méthodes de recherche et de vérification :

1const names = ["Blue", "Rex", "Bronto", "Triki"];
2const levels = [8, 10, 2, 3];
3
4console.log(names.indexOf("Rex"));              // 1
5console.log(names.includes("Delta"));           // false
6console.log(levels.find(level => level > 5));   // 8 - la première correspondance
7console.log(levels.some(level => level >= 10)); // true - au moins un
8console.log(levels.every(level => level > 0));  // true - tous

indexOf() renvoie la position ou -1, includes() répond oui ou non, et find() rend le premier élément correspondant ou undefined. some() et every() s'arrêtent dès qu'ils connaissent la réponse.

flat() et flatMap() - aplatir

Les données des secteurs arrivent souvent sous forme de tableaux dans des tableaux. La méthode flat() d'ES2019 les aplatit d'un niveau, et flatMap() combine map() avec cet aplatissement :

1const sectors = [["Blue", "Delta"], ["Rex"], ["Bronto", "Triki"]];
2console.log(sectors.flat()); // ["Blue", "Delta", "Rex", "Bronto", "Triki"]
3
4const zones = [
5  { id: "A", dinosaurs: ["Blue", "Delta"] },
6  { id: "B", dinosaurs: ["Rex"] }
7];
8const everyone = zones.flatMap(zone => zone.dinosaurs);
9console.log(everyone); // ["Blue", "Delta", "Rex"]

Un simple map() renverrait ici un tableau de tableaux, alors que flatMap() donne directement une seule liste d'habitants. Tu aplatiras les imbrications plus profondes en indiquant la profondeur, par exemple flat(2).

La composition de fonctions : pipe et compose

La chaîne filter() et map() exige un tableau. Les fonctions ordinaires, elles, se combinent par composition : pipe() fait passer une valeur par des étapes successives de gauche à droite, et nous allons justement la construire avec reduce() :

1const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
2
3const onlyCarnivores = list => list.filter(dino => dino.diet === "carnivore");
4const toNames = list => list.map(dino => dino.name);
5
6const carnivoreNames = pipe(onlyCarnivores, toNames);
7console.log(carnivoreNames([
8  { name: "Blue", diet: "carnivore" },
9  { name: "Bronto", diet: "herbivore" }
10])); // ["Blue"]

L'accumulateur est ici la valeur elle-même, et chaque fonction s'applique au résultat de la précédente. compose() fait la même chose de droite à gauche, généralement avec reduceRight(). Tu approfondiras la composition dans la location sur la programmation fonctionnelle.

Voici le chemin vers la maîtrise des méthodes de tableaux : les manipulations de base (push, pop, shift, unshift), la recherche (indexOf, includes, find), les transformations (map, filter, reduce), les agrégations (every, some, flat), et au sommet les motifs compose et pipe. Dans le laboratoire ci-dessous, tu compléteras les rapports selon les commentaires TODO.

Retiens bien : map() transforme, filter() tamise, et reduce() condense tout le troupeau en un seul résultat - ensemble, elles forment la chaîne de production du laboratoire.

Code de cette leçon : index.js
1// Methodes superieures des tableaux : map, filter, reduce
2
3const dinosaurs = [
4  { name: "Blue", age: 4, diet: "carnivore", dangerLevel: 8 },
5  { name: "Rex", age: 12, diet: "carnivore", dangerLevel: 10 },
6  { name: "Bronto", age: 8, diet: "herbivore", dangerLevel: 2 },
7  { name: "Triki", age: 6, diet: "herbivore", dangerLevel: 3 },
8  { name: "Ptero", age: 5, diet: "carnivore", dangerLevel: 6 }
9];
10
11// === MAP ===
12console.log("=== map() ===");
13const names = dinosaurs.map(dino => dino.name);
14console.log("Noms :", names);
15
16// TODO : utilise map() pour creer un tableau d'objets
17// avec les champs : label (name + species) et ageInMonths (age * 12)
18
19// === FILTER ===
20console.log("\n=== filter() ===");
21const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
22console.log("Carnivores :", carnivores.map(d => d.name));
23
24// TODO : filtre les dinosaures dont dangerLevel >= 7
25
26// === REDUCE ===
27console.log("\n=== reduce() ===");
28const totalAge = dinosaurs.reduce((sum, dino) => sum + dino.age, 0);
29console.log("Somme des ages :", totalAge);
30console.log("Age moyen :", totalAge / dinosaurs.length);
31
32// TODO : utilise reduce() pour calculer le niveau de menace total
33
34// === PIPELINE (chainage de methodes) ===
35console.log("\n=== Pipeline ===");
36// TODO : enchaine filter -> map -> reduce
37// Filtre les carnivores, extrais dangerLevel, additionne
38const totalDangerOfCarnivores = dinosaurs
39  .filter(dino => dino.diet === "carnivore")
40  .map(dino => dino.dangerLevel)
41  .reduce((sum, level) => sum + level, 0);
42
43console.log("Menace totale des carnivores :", totalDangerOfCarnivores);

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que renvoie la méthode map() en JavaScript ?

  2. 2. À quoi sert la méthode filter() sur les tableaux ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Transforme le tableau de vélociraptors en ajoutant une propriété trainingComplete à chacun.

  • Éditeur de code

    Filtre le tableau pour ne garder que les vélociraptors alpha.

  • Clique dans l'ordre

    Ordonne correctement la syntaxe de la méthode map :

  • Éditeur de code

    Calcule l'âge moyen de la meute avec reduce().

  • Clique dans l'ordre

    Ordonne correctement la syntaxe de la méthode filter :

  • Tri vertical

    Ordonne le parcours d'apprentissage des méthodes de tableau, du plus simple au plus avancé :

Articles utiles