Inspiré par l’article original d’Etienne Talbot
Introduction
Dans l’immensité de la galaxie JavaScript, certaines méthodes brillent comme les phares de l’Astronomican. Si vous êtes un nouveau recrue de l’Adeptus Mechanicus du développement web, il est temps de vous familiariser avec les armes les plus puissantes de votre arsenal : .map()
, .reduce()
, et .filter()
. Ces méthodes sont essentielles pour tout Tech-prêtre désireux de manipuler efficacement les données sans sombrer dans l’hérésie du code spaghetti.
Ces outils existent dans de nombreux langages de programmation, comme autant de variantes de bolters dans l’arsenal d’un Space Marine.
.map()
Voyons comment .map()
fonctionne avec un exemple simple. Imaginez que vous avez reçu un tableau contenant plusieurs objets – chacun représentant un Space Marine. Votre mission, dictée par le Chapitre, est d’extraire uniquement l’identifiant de service de chaque guerrier.
// Ce que vous avez
var spaceMarines = [
{ id: 20, name: 'Capitaine Titus' },
{ id: 24, name: 'Sergent Sidonus' },
{ id: 56, name: 'Bibliothécaire Tigurius' },
{ id: 88, name: 'Chapelain Cassius' }
];
// Ce dont vous avez besoin
[20, 24, 56, 88]
Plusieurs tactiques s’offrent à vous. Vous pourriez utiliser une boucle .forEach()
, .for(...of)
, ou un simple .for()
pour accomplir votre mission.
Comparons ces approches comme si nous comparions différentes stratégies de combat !
En utilisant .forEach()
:
var marineIds = [];
spaceMarines.forEach(function (marine) {
marineIds.push(marine.id);
});
Remarquez comment vous devez créer un tableau vide avant de commencer ? C’est comme préparer une escouade vide avant d’y assigner des Space Marines. Voyons maintenant la puissance de .map()
:
var marineIds = spaceMarines.map(function (marine) {
return marine.id
});
Et avec les fonctions fléchées (comme un tir de plasma bien précis) :
const marineIds = spaceMarines.map(marine => marine.id);
Comment fonctionne .map()
? Il prend 2 arguments : une fonction de rappel et un contexte optionnel (qui sera considéré comme « this » dans le callback). Le callback s’exécute pour chaque valeur du tableau et renvoie chaque nouvelle valeur dans le tableau résultant.
N’oubliez pas que le tableau résultant aura toujours la même longueur que le tableau d’origine, comme une compagnie de Space Marines après une formation standard.
.reduce()
Tout comme .map()
, .reduce()
exécute également un callback pour chaque élément d’un tableau. Ce qui diffère, c’est que .reduce()
transmet le résultat de ce callback (l’accumulateur) d’un élément à l’autre, comme la sagesse d’un Chapelain transmise aux novices du Chapitre.
L’accumulateur peut être pratiquement n’importe quoi (entier, chaîne, objet, etc.) et doit être initialisé ou passé lors de l’appel à .reduce()
.
Prenons un autre exemple ! Supposons que vous disposiez d’un tableau de pilotes de la Flotte Impériale avec leurs années d’expérience :
var imperioPilots = [
{
id: 10,
name: "Marcus Graywind",
years: 14,
},
{
id: 2,
name: "Hector Stormraven",
years: 30,
},
{
id: 41,
name: "Livia Nightblade",
years: 16,
},
{
id: 99,
name: "Brutus Voidwalker",
years: 22,
}
];
Votre Seigneur de Guerre a besoin de connaître le total des années d’expérience. Avec .reduce()
, c’est aussi simple que de réciter le Litanies de la Foi :
var totalYears = imperioPilots.reduce(function (accumulator, pilot) {
return accumulator + pilot.years;
}, 0);
Notez que j’ai défini la valeur initiale à 0. Après l’exécution du callback pour chaque pilote, .reduce()
renverra la valeur finale de notre accumulateur (82 années de service pour l’Empereur).
Avec les fonctions fléchées d’ES6, c’est encore plus concis qu’une communication en code binaire :
const totalYears = imperioPilots.reduce((acc, pilot) => acc + pilot.years, 0);
Et si nous voulions trouver le pilote le plus expérimenté ? Pour l’Empereur, nous utiliserons encore .reduce()
:
var veteranPilot = imperioPilots.reduce(function (oldest, pilot) {
return (oldest.years || 0) > pilot.years ? oldest : pilot;
}, {});
J’ai nommé mon accumulateur « oldest ». Mon callback compare l’expérience et sélectionne le pilote le plus chevronné, comme le ferait un Maître de Chapitre pour choisir son successeur.
.filter()
Que faire si vous disposez d’un tableau mais ne souhaitez que certains éléments ? C’est là que .filter()
entre en scène, tel un Bibliothécaire séparant les loyalistes des hérétiques !
Voici nos données :
var battleBrothers = [
{
id: 2,
name: "Argus Valorian",
chapter: "Ultramarines",
},
{
id: 8,
name: "Khorvan Bloodfist",
chapter: "World Eaters",
},
{
id: 40,
name: "Malus Darkblade",
chapter: "Night Lords",
},
{
id: 66,
name: "Gideon Ravenor",
chapter: "Ultramarines",
}
];
Nous voulons deux tableaux : un pour les Space Marines loyaux, l’autre pour les traîtres du Chaos. Avec .filter()
, c’est aussi simple que de séparer le bon grain de l’ivraie :
var loyalMarines = battleBrothers.filter(function (marine) {
return marine.chapter === "Ultramarines";
});
var chaosMarines = battleBrothers.filter(function (marine) {
return marine.chapter === "World Eaters" || marine.chapter === "Night Lords";
});
Ou avec les fonctions fléchées :
const loyalMarines = battleBrothers.filter(marine => marine.chapter === "Ultramarines");
const chaosMarines = battleBrothers.filter(marine =>
marine.chapter === "World Eaters" || marine.chapter === "Night Lords");
En résumé, si la fonction de callback renvoie true, l’élément sera dans le tableau résultant. Si elle renvoie false, il sera banni comme un hérétique.
Combiner .map(), .reduce(), et .filter()
Puisque les trois sont appelés sur des tableaux et que .map()
et .filter()
renvoient tous deux des tableaux, nous pouvons facilement enchaîner nos appels comme une série de manœuvres tactiques.
Voici un autre exemple avec des données sur les Space Marines :
var battleforce = [
{
id: 5,
name: "Marneus Calgar",
meleeCombat: 98,
rangedCombat: 56,
hasPsychicPowers: true,
},
{
id: 82,
name: "Varro Tigurius",
meleeCombat: 73,
rangedCombat: 59,
hasPsychicPowers: true,
},
{
id: 22,
name: "Cato Sicarius",
meleeCombat: 90,
rangedCombat: 79,
hasPsychicPowers: false,
},
{
id: 15,
name: "Uriel Ventris",
meleeCombat: 83,
rangedCombat: 77,
hasPsychicPowers: false,
},
{
id: 11,
name: "Ortan Cassius",
meleeCombat: 81,
rangedCombat: 65,
hasPsychicPowers: true,
},
];
Notre mission : calculer la puissance de combat totale des Space Marines dotés de pouvoirs psychiques. Procédons par étapes, comme le ferait tout bon tacticien !
D’abord, filtrons les Space Marines sans pouvoirs psychiques :
var psykers = battleforce.filter(function (marine) {
return marine.hasPsychicPowers;
});
// Résultat : [{...}, {...}, {...}] (Calgar, Tigurius et Cassius)
Maintenant, créons un tableau avec la puissance de combat totale de chaque psyker :
var psykerScores = psykers.map(function (psyker) {
return psyker.meleeCombat + psyker.rangedCombat;
});
// Résultat : [154, 132, 146]
Et utilisons .reduce()
pour obtenir le total :
var totalPsykerPower = psykerScores.reduce(function (acc, score) {
return acc + score;
}, 0);
// Résultat : 432
Maintenant, combinons tout cela comme une stratégie de bataille parfaite :
var totalPsykerPower = battleforce
.filter(function (marine) {
return marine.hasPsychicPowers;
})
.map(function (psyker) {
return psyker.meleeCombat + psyker.rangedCombat;
})
.reduce(function (acc, score) {
return acc + score;
}, 0);
Et avec les fonctions fléchées, c’est aussi élégant qu’une lame énergétique en plein combat :
const totalPsykerPower = battleforce
.filter(marine => marine.hasPsychicPowers)
.map(psyker => psyker.meleeCombat + psyker.rangedCombat)
.reduce((acc, score) => acc + score, 0);
Par l’Empereur ! 💥
Pourquoi ne pas utiliser .forEach() ?
Avant, j’utilisais des boucles for partout, comme un novice qui n’utiliserait que son bolter standard. Mais une fois que j’ai commencé à manipuler des données d’API, j’ai découvert la puissance supérieure de ces méthodes avancées.
Formatage
Supposons que vous deviez afficher une liste de commandants de la Flotte Impériale :
var commanders = [
{
name: "Artemis Voidbreaker",
rank: "Lord Admiral",
},
{
name: "Horath Stormborn",
rank: "Fleet Captain",
},
]
L’API vous donne ces données, mais vous n’avez besoin que du grade et du nom de famille… Vous devez formater les données pour le rapport au Haut Commandement. Votre application a besoin d’une vue d’ensemble et d’une vue détaillée pour chaque commandant.
Avec .forEach()
, vous finiriez avec une solution complexe :
var results = [];
commanders.forEach(function (commander) {
var formatted = formatCommander(commander);
results.push(formatted);
});
Pourquoi utiliser deux rituels quand un seul suffit ?
var results = commanders.map(formatCommander);
Des tests plus efficaces que les simulations de la Flotte
Si vous écrivez des tests unitaires pour votre code (comme tout bon Tech-prêtre devrait le faire), tester ces fonctions devient aussi simple que de vérifier un bolter :
Fournissez des données d’entrée et attendez le résultat. « Que se passe-t-il si je tire cette munition ? » Moins de manipulations, moins de préparatifs. Simple et efficace.
Conclusion
Pour la gloire de l’Empereur et la pureté de votre code, adoptez ces méthodes puissantes ! Remplacez vos anciennes boucles for par .map()
, .reduce()
et .filter()
quand cela est approprié. Votre code deviendra aussi efficace qu’une compagnie de Space Marines bien entraînée.
Que la Machine-Dieu guide votre clavier et que vos fonctions s’exécutent sans erreur. N’oubliez pas : un code propre est un code béni !
Cet article est une adaptation thématique inspirée par le travail original d’Etienne Talbot sur les méthodes JavaScript .map(), .reduce() et .filter().
In the grim darkness of the far future, there is only… clean code.