Question 1Après avoir exécuté prices.map((price) => price * 2), que devient le tableau prices d'origine ?
map et filter — transformer ou filtrer un tableau
map transforme chaque élément, filter ne garde que ceux qui correspondent. Au programme : tableau d'origine intact, chaînage et argument index.
Que tu extraies les prix TTC d'une carte ou que tu ne gardes que les produits en stock, tu finis toujours par réécrire les mêmes lignes : déclarer un tableau vide, puis appeler push dans un forEach.
Cet article présente map, qui transforme les éléments, et filter, qui ne garde que les éléments qui remplissent une condition.
Transformer chaque élément dans un nouveau tableau — map
Imagine que tu veux convertir tous les prix hors taxes de la carte d'un café en prix TTC. Le résultat doit être un tableau dans le même ordre que les 3 articles d'origine, où seuls les prix ont changé. Tu peux calculer chaque prix avec forEach, mais pour rassembler les résultats, tu dois créer toi-même un tableau et le remplir tout au long de la boucle.
La méthode de tableau map (une méthode qui passe chaque élément à un callback et renvoie un nouveau tableau contenant les valeurs de retour, dans l'ordre) se charge à la fois de créer le tableau de résultats et de parcourir les éléments. La fonction que tu lui passes décrit seulement comment transformer un élément, et renvoie le résultat.
const menuItems = [
{ name: "Café filtre", price: 480 },
{ name: "Café latte", price: 540 },
{ name: "Cheesecake", price: 620 },
];
// Seule la transformation d'un élément va dans la fonction
const toTaxIncluded = (item) => Math.floor(item.price * 1.1);
// toTaxIncluded est appelée une fois par élément, et ses valeurs de retour remplissent le nouveau tableau
const taxIncluded = menuItems.map(toTaxIncluded);
console.log(taxIncluded.join(", ")); // 528, 594, 682
console.log(taxIncluded.length); // 3
console.log(menuItems.length); // 3
taxIncluded dans le même ordre que les éléments d'origine. map ne change jamais le nombre d'éléments, seulement leurs valeurs.map se contente de passer les éléments un par un au callback et d'ajouter au nouveau tableau ce qui revient : le nombre d'éléments ne peut donc pas changer. Pour filtrer un tableau, ce qui change justement ce nombre, il faut une autre méthode.
forEach ne renvoie rien
forEach se contente d'appeler la fonction que tu lui passes ; il ne rassemble pas les valeurs de retour. Il renvoie toujours undefined : même si tu écris const list = menuItems.forEach(toTaxIncluded);, list vaut undefined. Pour récupérer les résultats sous forme de tableau, utilise map.
Ne garder que les éléments qui correspondent — filter et la taille du résultat
Imagine que tu veux lister uniquement les livres en stock. Comme map ne change jamais le nombre d'éléments, il ne peut pas écarter ceux qui sont en rupture, et tu te retrouves à écrire un if dans un forEach pour appeler push.
filter (une méthode qui renvoie un nouveau tableau contenant uniquement les éléments pour lesquels le callback a renvoyé true) se charge de ce filtrage. La fonction que tu lui passes reçoit un élément et renvoie un booléen qui indique s'il faut le garder.
const books = [
{ title: "SQL en pratique", stock: 0 },
{ title: "Les bases du réseau", stock: 5 },
{ title: "Linux illustré", stock: 2 },
];
// Une fonction qui renvoie un booléen : on garde ou pas
const isInStock = (book) => book.stock > 0;
// Seuls les éléments qui ont renvoyé true entrent dans le nouveau tableau
const available = books.filter(isInStock);
console.log(available.length); // 2
console.log(available[0].title); // Les bases du réseau
console.log(books.length); // 3
true entrent dans available. La taille baisse d'autant d'éléments qu'il y en a d'écartés, et ceux qui restent se décalent vers l'avant pour combler les trous.available ne connaît que les nouvelles positions : l'index que chaque élément avait dans books est perdu. Tu verras à la fin de cet article comment travailler avec la position d'origine. Le tableau ci-dessous résume comment chacune des 3 méthodes utilise la valeur de retour de la fonction que tu lui passes.
| Méthode | Valeur de retour du callback | Taille du tableau obtenu |
|---|---|---|
| map | Va dans le nouveau tableau | Identique à l'original |
| filter | Si true, l'élément est gardé | Identique à l'original ou plus petite |
| forEach | Ignorée | Aucun tableau créé (renvoie undefined) |
Avec des accolades, il faut un return
Si tu écris le corps entre accolades, comme dans (book) => { book.stock > 0 }, la valeur de retour est undefined tant que tu n'écris pas return. Comme undefined est falsy, aucun élément n'est gardé et tu obtiens un tableau vide. Pour une vérification tenant sur une ligne, omets les accolades.
Filtrer, puis transformer — chaîner filter et map
Imagine que tu veux transformer en chaînes à afficher uniquement les cours publiés. Tu pourrais ranger le résultat de filter dans une variable, puis appeler .map(...) dessus sur une deuxième ligne, mais tu donnerais alors un nom à un tableau intermédiaire dont tu ne te serviras plus jamais.
filter renvoie lui aussi un tableau : tu peux donc appeler .map(...) directement sur sa valeur de retour. Enchaîner ainsi les appels de méthodes s'appelle le chaînage (appeler la méthode suivante sur la valeur de retour de la précédente).
const courses = [
{ title: "Débuter avec Git", minutes: 45, published: true },
{ title: "Exercices SQL", minutes: 90, published: false },
{ title: "Linux pour débutants", minutes: 60, published: true },
];
// Ne garde que les cours publiés, puis transforme le résultat en chaînes à afficher
// Un . en début de ligne appelle la méthode sur le résultat de la ligne précédente (c'est toujours une seule expression)
const labels = courses
.filter((course) => course.published) // published est déjà un booléen, on le renvoie tel quel
.map((course) => `${course.title} (${course.minutes} min)`);
console.log(labels.join(" / "));
// Débuter avec Git (45 min) / Linux pour débutants (60 min)
console.log(labels.length); // 2
console.log(courses.length); // 3
.map lit le tableau renvoyé par filter sans que ce tableau reçoive jamais de nom. Chaque étape de la chaîne crée un nouveau tableau.Pour inspecter le résultat intermédiaire, range le résultat de filter dans une variable, affiche-le avec console.log, puis appelle .map(...) sur cette variable. Une fois la vérification terminée, tu peux revenir à la forme chaînée.
Récupérer la position — l'index du callback
Imagine que tu veux ajouter un rang, comme « n° 1 » ou « n° 2 », à un classement. Les éléments eux-mêmes ne stockent pas de rang : on est donc tenté de garder un compteur en dehors du callback et de lui ajouter 1 à chaque appel.
map et filter passent l'élément en premier argument du callback, et la position de cet élément (son index, compté à partir de 0) en deuxième. Les arguments sont transmis dans l'ordre, donc même si tu n'as besoin que de l'index, tu dois quand même écrire le premier paramètre. Si tu nommes ce deuxième paramètre index, tu peux t'en servir aussi bien pour afficher un rang que pour vérifier si un élément fait partie des premiers.
const ranking = ["Écouteurs sans fil", "Batterie externe", "Support de téléphone", "Hub USB"];
// Le deuxième argument est la position, comptée à partir de 0
const lines = ranking.map((item, index) => `N° ${index + 1} : ${item}`);
console.log(lines.join("\n"));
// N° 1 : Écouteurs sans fil
// N° 2 : Batterie externe
// N° 3 : Support de téléphone
// N° 4 : Hub USB
// Le callback de filter reçoit les deux mêmes arguments
const topThree = ranking.filter((item, index) => index < 3);
console.log(topThree.length); // 3
L'index passé à filter est la position dans le tableau d'origine. Le fait que les éléments gardés se décalent ensuite vers l'avant n'a aucun effet sur la vérification : index < 3 désigne donc les 3 premiers éléments du tableau d'origine.
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Dans courses.filter(isPublished).map(toLabel), quel tableau map lit-il ?
Question 3Dans le callback de ranking.map((item, index) => ...), qu'est-ce qui est passé à index ?