frontend / javascript
Tableaux : méthodes avancées
Explication
Ce que vous allez apprendre
- Choisir entre
map,filteretreduceselon le résultat recherché - Expliquer pourquoi ces trois méthodes ne modifient jamais le tableau d'origine
- Repérer les méthodes qui mutent le tableau (
sort,push,splice) et celles qui ne mutent pas - Trier correctement un tableau de nombres en évitant le piège du tri alphabétique par défaut
- Chaîner plusieurs opérations sur un tableau pour écrire un pipeline de transformation lisible
Dans quel contexte ?
Une équipe reçoit un signalement : sur le tableau de bord des ventes, la fonction trierMontants(montants) classe [100, 25, 9] en [100, 25, 9] au lieu de [9, 25, 100]. En inspectant le code, on trouve montants.sort() sans fonction de comparaison : sort compare les éléments comme des chaînes de caractères par défaut, donc "100" passe avant "25" car "1" < "2". Ce genre de bug silencieux, qui ne plante jamais mais donne un résultat faux, est l'un des pièges les plus fréquents de l'API Array.
1. Décrire le résultat plutôt que la mécanique
Manipuler des listes est l'une des tâches les plus fréquentes en programmation. JavaScript propose une API riche sur les tableaux qui permet, la plupart du temps, de se passer des boucles for classiques : on décrit CE QUE l'on veut obtenir, pas COMMENT l'obtenir pas à pas.
2. Les trois méthodes à connaître en premier
Trois méthodes forment le socle de ce style dit "fonctionnel". map transforme chaque élément en un nouveau tableau, filter ne garde que les éléments qui satisfont une condition, et reduce accumule tous les éléments en une seule valeur (une somme, un objet, n'importe quoi).
| Méthode | Entrée → Sortie | Cas d'usage typique |
|---|---|---|
map | Tableau → Tableau (même longueur) | Transformer chaque élément (ex : extraire un champ) |
filter | Tableau → Tableau (longueur ≤) | Garder certains éléments selon une condition |
reduce | Tableau → Valeur unique | Sommer, regrouper, construire un objet |
3. Un point commun rassurant : aucune ne mute
Ce qui rend ces trois méthodes agréables à utiliser, c'est qu'elles ne modifient jamais le tableau d'origine : elles en retournent toujours un nouveau. Cela rend le code plus prévisible, surtout quand plusieurs parties du programme partagent la même donnée.
Prérequis
Cette leçon suppose que vous êtes à l'aise avec les fonctions fléchées ((n) => n * 2) et le concept de fonction passée en argument, vus dans la leçon précédente sur les fonctions et closures.
4. Il reste un problème : certaines méthodes, elles, mutent
À l'inverse, des méthodes plus anciennes comme sort, push ou splice modifient directement le tableau sur lequel elles sont appelées. C'est une source d'effets de bord inattendus si on l'oublie, d'où l'ajout récent (ES2023) d'équivalents immuables comme toSorted.
5. Le piège le plus répandu
sort() illustre bien ce risque : sans fonction de comparaison, il trie les éléments comme des chaînes de caractères, pas comme des nombres — [10, 2, 1] devient [1, 10, 2]. Retenez le réflexe : toujours fournir un comparateur explicite dès qu'on trie des nombres.
Piège fréquent
tableau.sort((a, b) => a - b) trie par ordre croissant ; oublier ce comparateur transforme silencieusement un tri numérique en tri alphabétique, sans la moindre erreur ou avertissement dans la console.
Cette leçon prolonge directement les bases vues précédemment (fonctions, portée) en les appliquant à la structure de données la plus utilisée du langage.
Commandes & code
Tableaux : méthodes avancées
L'API riche des Array, au coeur de la programmation JS idiomatique.
const nombres = [5, 12, 8, 3, 19, 7];
// map / filter / reduce : le trio fondamental de la programmation fonctionnelle en JS
const doubles = nombres.map((n) => n * 2);
const pairs = nombres.filter((n) => n % 2 === 0);
const somme = nombres.reduce((acc, n) => acc + n, 0);
const max = nombres.reduce((acc, n) => Math.max(acc, n), -Infinity);
// Chainage : lisible et performant pour des transformations en pipeline
const resultat = nombres
.filter((n) => n > 5)
.map((n) => n * 10)
.reduce((acc, n) => acc + n, 0);
// find / findIndex / findLast (ES2023) : recherche du premier element correspondant
const premierGrand = nombres.find((n) => n > 10);
const indexPremierGrand = nombres.findIndex((n) => n > 10);
const dernierPetit = nombres.findLast((n) => n < 10);
// some / every : tests booleens sur l'ensemble
console.log(nombres.some((n) => n > 15)); // au moins un element valide
console.log(nombres.every((n) => n > 0)); // tous les elements valident
// sort : ATTENTION, mute le tableau original et trie par defaut en chaines !
const nonTries = [10, 1, 21, 2];
console.log([...nonTries].sort()); // [1, 10, 2, 21] -- BUG : tri lexicographique !
console.log([...nonTries].sort((a, b) => a - b)); // [1, 2, 10, 21] -- correct : comparateur numerique
// toSorted / toReversed / toSpliced (ES2023) : versions IMMUABLES (ne mutent pas l'original)
const trieSansMutation = nonTries.toSorted((a, b) => a - b);
console.log(nonTries); // inchange
// flat / flatMap : aplatir des tableaux imbriques
const imbrique = [1, [2, 3], [4, [5, 6]]];
console.log(imbrique.flat()); // [1, 2, 3, 4, [5, 6]] -- profondeur 1 par defaut
console.log(imbrique.flat(Infinity)); // aplatissement complet
const phrases = ["bonjour le monde", "comment vas tu"];
const mots = phrases.flatMap((p) => p.split(" ")); // map + flat en une passe
// Destructuring et spread sur les tableaux
const [premier, deuxieme, ...reste] = nombres;
const fusionne = [...nombres, ...doubles];
const copie = [...nombres]; // copie superficielle
// Array.from : creer un tableau depuis un iterable ou array-like
console.log(Array.from({ length: 5 }, (_, i) => i * i)); // [0, 1, 4, 9, 16]
console.log(Array.from("hello")); // ['h','e','l','l','o']
console.log(Array.from(new Set([1, 2, 2, 3]))); // deduplication
// Array.of vs Array() : piege du constructeur avec un seul argument numerique
console.log(Array(5)); // [ <5 vides> ] -- tableau de longueur 5
console.log(Array.of(5)); // [5] -- tableau contenant l'element 5
// includes vs indexOf : includes gere correctement NaN
console.log([1, NaN, 3].indexOf(NaN)); // -1 -- indexOf utilise ===, ne trouve pas NaN
console.log([1, NaN, 3].includes(NaN)); // true -- includes utilise SameValueZero
// Group by (ES2024, methode Object.groupBy)
const utilisateurs = [
{ nom: "Alice", role: "admin" },
{ nom: "Bob", role: "user" },
{ nom: "Cy", role: "admin" },
];
const parRole = Object.groupBy(utilisateurs, (u) => u.role);
console.log(parRole); // { admin: [Alice, Cy], user: [Bob] }
// Performance : eviter de muter un tableau pendant qu'on l'itere avec forEach
const items = [1, 2, 3];
// items.forEach((item, i) => items.splice(i, 1)); // comportement imprevisible, a EVITER| Méthode | Mute l'original ? | Usage |
|---|---|---|
push, pop, sort, splice, reverse | Oui | Mutation directe |
map, filter, slice, concat | Non | Transformation pure |
toSorted, toReversed, toSpliced (ES2023) | Non | Équivalents immuables |
Résumé
sort()sans comparateur trie lexicographiquement : toujours fournir(a, b) => a - bpour des nombres.toSorted/toReversed/toSpliced(ES2023) offrent des équivalents immuables aux méthodes mutantes.includes()détecteNaNcorrectement (SameValueZero), contrairement àindexOf().flatMapcombinemap+flat(1)en une seule passe, plus efficace.
Exercices pratiques
Mission : réparer le classement des ventes
Objectif : Corriger un tri numérique cassé, puis construire un pipeline immuable de transformation sur les données de ventes.
Contexte
Le tableau de bord des ventes de Technologik affiche un classement des montants qui semble aléatoire :
const montants = [100, 25, 9, 400, 2];
const classement = montants.sort();
console.log(classement); // [100, 2, 25, 400, 9]Le code appelant trierMontants réutilise ensuite le tableau montants original ailleurs dans l'application, ce qui provoque un second bug plus discret. Tu dois corriger le tri, éviter la mutation involontaire, puis construire un pipeline qui calcule le total des ventes supérieures à 50.