La programmation fonctionnelle fait actuellement des vagues dans le monde du développement. Et pour cause: les techniques fonctionnelles peuvent vous aider à écrire davantage de code déclaratif, plus facile à comprendre d'un coup d'œil, à refactoriser et à tester..
L’une des pierres angulaires de la programmation fonctionnelle est son utilisation particulière des listes et de leurs opérations. Et ces choses sont exactement ce que le son ressemble: tableaux de choses et tout ce que vous leur faites. Mais la mentalité fonctionnelle les traite un peu différemment de ce à quoi on pourrait s'attendre.
Cet article examinera de près ce que j'aime appeler les "trois grandes" opérations de liste: carte
, filtre
, et réduire
. Envisager ces trois fonctions en tête est une étape importante pour pouvoir écrire du code fonctionnel propre et ouvrir les portes aux techniques extrêmement puissantes de la programmation fonctionnelle et réactive..
Cela signifie également que vous ne devrez jamais écrire un pour
boucle encore.
Curieuse? Plongeons dedans.
Souvent, nous avons besoin de prendre un tableau et de modifier chaque élément de celui-ci de la même manière. Des exemples typiques en sont la quadrature de chaque élément d'un tableau de nombres, l'extraction du nom d'une liste d'utilisateurs ou l'exécution d'une expression régulière sur un tableau de chaînes..
carte
est une méthode conçue pour faire exactement cela. C'est défini sur Array.prototype
, afin que vous puissiez l'appeler sur n'importe quel tableau, et il accepte un rappel comme premier argument.
Quand vous appelez carte
sur un tableau, il exécute ce rappel sur tous ses éléments, renvoyant un Nouveau tableau avec toutes les valeurs renvoyées par le rappel.
Sous la capuche, carte
passe trois arguments à votre rappel:
Regardons du code.
carte
en pratiqueSupposons que nous ayons une application qui gère un tableau de vos tâches pour la journée. Chaque tâche
est un objet, chacun avec un prénom
et durée
propriété:
// Les durées sont en minutes var tasks = ['nom': 'Écrire pour Envato Tuts +', 'durée': 120, 'nom': 'Entraînement', 'durée': 60, 'nom' : 'Tergiverser sur Duolingo', 'durée': 240];
Supposons que nous voulions créer un nouveau tableau avec juste le nom de chaque tâche afin de pouvoir examiner tout ce que nous avons accompli aujourd'hui. Utilisant un pour
boucle, nous écririons quelque chose comme ceci:
var task_names = []; pour (var i = 0, max = tasks.length; i < max; i += 1) task_names.push(tasks[i].name);
JavaScript offre également une pour chaque
boucle. Il fonctionne comme un pour
boucle, mais gère tout le désordre de vérifier notre index de boucle par rapport à la longueur du tableau pour nous:
var task_names = []; tasks.forEach (function (task) nom_tâche.push (task.name););
En utilisant carte
, nous pouvons écrire:
var nom_tâche = tasks.map (fonction (tâche, index, tableau) return task.name;);
J'inclus le indice
et tableau
paramètres pour vous rappeler qu'ils sont là si vous en avez besoin. Puisque je ne les ai pas utilisés ici, vous pouvez les laisser de côté et le code fonctionnerait très bien..
Il existe quelques différences importantes entre les deux approches:
carte
, vous n'avez pas à gérer l'état de la pour
boucle toi.pousser
dans ça. carte
renvoie le produit fini en une seule fois, nous pouvons donc simplement affecter la valeur de retour à une nouvelle variable.revenir
déclaration dans votre rappel. Si vous ne le faites pas, vous aurez un nouveau tableau rempli avec indéfini
. Il s'avère que, tout des fonctions que nous examinerons aujourd'hui partagent ces caractéristiques.
Le fait que nous n'ayons pas à gérer manuellement l'état de la boucle rend notre code plus simple et plus facile à gérer. Le fait que nous puissions agir directement sur l'élément au lieu d'avoir à indexer dans le tableau rend les choses plus lisibles..
Utilisant un pour chaque
boucle résout ces deux problèmes pour nous. Mais carte
a toujours au moins deux avantages distincts:
pour chaque
résultats indéfini
, de sorte qu'il ne chaîne avec d'autres méthodes de tableau. carte
retourne un tableau, donc vous pouvez enchaîner avec d'autres méthodes de tableau.carte
résultatsun tableau avec le produit fini, plutôt que de nous obliger à muter un tableau à l'intérieur de la boucle. Garder le nombre de places où vous modifiez l'état à un minimum absolu est un principe important de la programmation fonctionnelle. Cela rend le code plus sûr et plus intelligible.
C’est également le bon moment pour signaler que si vous êtes dans Node, testez ces exemples dans la console du navigateur Firefox, ou utilisez Babel ou Traceur, vous pouvez écrire ceci plus précisément avec les fonctions de flèche ES6:
var task_names = tasks.map ((task) => task.name);
Les fonctions fléchées nous laissent de côté les revenir
mot clé en une ligne.
Ça ne devient pas beaucoup plus lisible que ça.
Le rappel que vous passez à carte
doit avoir un explicite revenir
déclaration, ou carte
va cracher un tableau plein de indéfini
. Il n’est pas difficile de se rappeler d’inclure un revenir
valeur, mais il n'est pas difficile d'oublier.
Si vous faire oublier, carte
ne va pas se plaindre. Au lieu de cela, il restituera discrètement un tableau rempli de rien. De telles erreurs silencieuses peuvent être étonnamment difficiles à résoudre.
Heureusement, c'est le seulement eu avec carte
. Mais c’est un piège assez commun que je dois souligner: assurez-vous toujours que votre rappel contient un revenir
déclaration!
Lire des implémentations est une partie importante de la compréhension. Alors, écrivons notre propre poids léger carte
pour mieux comprendre ce qui se passe sous le capot. Si vous souhaitez voir une implémentation de qualité production, consultez le polyfill de Mozilla sur MDN.
var map = fonction (tableau, rappel) var new_array = []; array.forEach (function (élément, index, tableau) new_array.push (callback (element));); return new_array; ; var nom_tâche = map (tâches, fonction (tâche) return task.name;);
Ce code accepte un tableau et une fonction de rappel comme arguments. Il crée ensuite un nouveau tableau. exécute le rappel sur chaque élément du tableau que nous avons transmis; pousse les résultats dans le nouveau tableau; et retourne le nouveau tableau. Si vous l'exécutez dans votre console, vous obtiendrez le même résultat qu'auparavant. Assurez-vous simplement d'initialiser les tâches
avant de le tester!
Pendant que nous utilisons une boucle for sous le capot, l'envelopper dans une fonction cache les détails et nous permet de travailler avec l'abstraction à la place..
Cela rend notre code plus déclaratif, dit-il quoi faire, pas Comment pour le faire. Vous apprécierez combien plus lisible, maintenable et erm, débogable cela peut rendre votre code.
La prochaine de nos opérations de tableau est filtre
. Cela fait exactement ce que cela ressemble: il faut un tableau, et filtre les éléments indésirables.
Comme carte
, filtre
est défini sur Array.prototype
. Il est disponible sur n'importe quel tableau, et vous lui passez un rappel comme premier argument. filtre
exécute ce rappel sur chaque élément du tableau et crache un Nouveau tableau contenant seulement les éléments pour lesquels le rappel a été renvoyé vrai
.
Aussi comme carte
, filtre
passe vos trois arguments de rappel:
filtre
surfiltre
en pratiqueRevenons sur notre exemple de tâche. Au lieu de tirer les noms de chaque tâche, supposons que je veuille obtenir une liste des tâches qui m'ont pris deux heures ou plus..
En utilisant pour chaque
, nous écririons:
var difficult_tasks = []; tasks.forEach (fonction (tâche) if (tâche.duration> = 120) difficulté_tasks.push (tâche););
Avec filtre
:
var difficult_tasks = tasks.filter (fonction (tâche) retour tâche.duration> = 120;); // Utiliser ES6 var difficult_tasks = tasks.filter ((task) => task.duration> = 120);
Ici, je suis allé de l'avant et laissé de côté le indice
et tableau
arguments à notre rappel, puisque nous ne les utilisons pas.
Juste comme carte
, filtre
nous permet:
pour chaque
ou pour
boucleLe rappel que vous passez à carte
doit inclure une instruction return si vous voulez qu'elle fonctionne correctement. Avec filtre
, vous devez également inclure une déclaration de retour, et vous doit assurez-vous qu'il renvoie une valeur booléenne.
Si vous oubliez votre déclaration, votre rappel vous sera renvoyé. indéfini
, lequel filtre
contraindra inutilement à faux
. Au lieu de générer une erreur, il retournera en silence un tableau vide!
Si vous suivez l’autre itinéraire et retournez quelque chose qui est n'est pas explicitement vrai
ou faux
, puis filtre
essaiera de comprendre ce que vous vouliez dire en appliquant les règles de coercition de JavaScript. Plus souvent qu'autrement, c'est un bug. Et, tout comme oublier votre déclaration de retour, ce sera une déclaration silencieuse.
Toujours assurez-vous que vos rappels incluent une déclaration de retour explicite. Et toujours assurez-vous que vos rappels dans filtre
revenir vrai
ou faux
. Votre santé mentale va vous remercier.
Encore une fois, la meilleure façon de comprendre un morceau de code est de… l'écrire. Roulons notre propre poids léger filtre
. Les bonnes personnes de Mozilla ont également un polyfill de force industrielle à lire..
var filtre = fonction (tableau, rappel) var filtré_array = []; array.forEach (function (élément, index, tableau) if (rappel (élément, index, tableau)) filtré_array.push (élément);); return filter_array; ;
carte
crée un nouveau tableau en transformant chaque élément d'un tableau, individuellement. filtre
crée un nouveau tableau en supprimant les éléments qui n'appartiennent pas. réduire
, d'autre part, prend tous les éléments d'un tableau, et réduit les en une seule valeur.
Juste comme carte
et filtre
, réduire
est défini sur Array.prototype
et ainsi disponible sur n'importe quel tableau, et vous passez un rappel comme premier argument. Mais il faut aussi un deuxième argument optionnel: la valeur pour commencer à combiner tous vos éléments de tableau dans.
réduire
passe vos quatre arguments de rappel:
réduire
surNotez que le rappel obtient un valeur précédente à chaque itération. A la première itération, il y a est pas de valeur précédente. C’est pourquoi vous avez la possibilité de passer réduire
une valeur initiale: Il s'agit de la "valeur précédente" pour la première itération, sinon il n'y en aurait pas.
Enfin, gardez à l’esprit que réduire
renvoie un valeur unique, ne pas un tableau contenant un seul élément. Ceci est plus important qu'il n'y parait, et j'y reviendrai dans les exemples.
réduire
en pratiquePuisque réduire
est la fonction que les gens trouvent le plus extra-terrestre au début, nous allons commencer par une étape à la fois, à travers quelque chose de simple.
Disons que nous voulons trouver la somme d'une liste de nombres. En utilisant une boucle, cela ressemble à ceci:
nombres var = [1, 2, 3, 4, 5], total = 0; nombres.pour chaque (fonction (nombre) total + = nombre;);
Bien que ce ne soit pas un mauvais cas d’utilisation pour pour chaque
, réduire
a toujours l'avantage de nous permettre d'éviter la mutation. Avec réduire
, nous écririons:
var total = [1, 2, 3, 4, 5] .reduce (function (précédent, actuel) retour précédent + actuel;, 0);
Tout d'abord, nous appelons réduire
sur notre liste de Nombres.
Nous lui transmettons un rappel, qui accepte la valeur précédente et la valeur actuelle comme arguments et renvoie le résultat de leur addition. Depuis que nous avons passé 0
comme second argument pour réduire
, ça va l'utiliser comme valeur de précédent
à la première itération.
Si on le prend pas à pas, ça ressemble à ça:
Itération | précédent | Actuel | Total |
---|---|---|---|
1 | 0 | 1 | 1 |
2 | 1 | 2 | 3 |
3 | 3 | 3 | 6 |
4 | 6 | 4 | dix |
5 | dix | 5 | 15 |
Si vous n'êtes pas un fan de tables, lancez cet extrait de code dans la console:
var total = [1, 2, 3, 4, 5] .reduce (fonction (previous, actuelle, index) var val = précédente + actuelle; console.log ("La valeur précédente est" + précédente + "; la valeur actuelle la valeur est "+ current +" et l'itération actuelle est "+ (index + 1)); return val;, 0); console.log ("La boucle est terminée et la valeur finale est" + total + ".");
Récapituler: réduire
itère sur tous les éléments d'un tableau, en les combinant comme vous le spécifiez dans votre rappel. A chaque itération, votre rappel a accès au précédent valeur, qui est le total jusqu'ici, ou valeur accumulée; la valeur actuelle; la indice actuel; et l'ensemble tableau, si vous en avez besoin.
Revenons à notre exemple de tâches. Nous avons reçu une liste de noms de tâches de carte
, et une liste filtrée de tâches qui a pris beaucoup de temps avec… bien, filtre
.
Et si nous voulions connaître le temps total passé à travailler aujourd'hui?
Utilisant un pour chaque
boucle, vous écririez:
var total_time = 0; tasks.forEach (function (task) // Le signe plus ne fait que coercer // task.duration d'une chaîne en un nombre total_time + = (+ task.duration););
Avec réduire
, cela devient:
var total_time = tasks.reduce (function (previous, current) retour previous + current;, 0); // Utilisation des fonctions de flèche var total_time = tasks.reduce ((previous, current) previous + current);
Facile.
C'est presque tout ce qu'il y a à faire. Presque, parce que JavaScript nous fournit une autre méthode peu connue, appelée réduire à droite
. Dans les exemples ci-dessus, réduire
commencé au premier élément du tableau, itérant de gauche à droite:
var array_of_arrays = [[1, 2], [3, 4], [5, 6]]; var concatenated = array_of_arrays.reduce (function (previous, current) return previous.concat (current);); console.log (concaténé); // [1, 2, 3, 4, 5, 6];
réduire à droite
fait la même chose, mais dans le sens opposé:
var array_of_arrays = [[1, 2], [3, 4], [5, 6]]; var concatenated = array_of_arrays.reduceRight (function (previous, current) return previous.concat (current);); console.log (concaténé); // [5, 6, 3, 4, 1, 2];
j'utilise réduire
tous les jours, mais je n'en ai jamais eu besoin réduire à droite
. Je pense que vous ne le ferez probablement pas non plus. Mais si jamais vous le faites, maintenant vous savez que c'est là.
Les trois gros coups avec réduire
sont:
revenir
réduire
renvoie une valeur uniqueHeureusement, les deux premiers sont faciles à éviter. Le choix de votre valeur initiale dépend de ce que vous faites, mais vous vous en rendrez vite compte..
Le dernier peut sembler un peu étrange. Si réduire
ne renvoie jamais qu'une seule valeur, pourquoi vous attendriez-vous à un tableau??
Il y a quelques bonnes raisons pour cela. Premier, réduire
renvoie toujours un seul valeur, pas toujours un seul nombre. Si vous réduisez un tableau de tableaux, par exemple, un seul tableau sera renvoyé. Si vous avez l'habitude de réduire les tableaux, il serait juste de s'attendre à ce qu'un tableau contenant un seul élément ne soit pas un cas particulier..
Deuxièmement, si réduire
fait renvoyer un tableau avec une valeur unique, il jouerait naturellement bien avec carte
et filtre
, et d'autres fonctions sur les tableaux que vous êtes susceptible d'utiliser avec elle.
Temps pour notre dernier coup d'oeil sous le capot. Comme d'habitude, Mozilla a un polyfill à l'épreuve des balles pour réduire si vous voulez le vérifier.
var reduction = function (tableau, rappel, initial) var accumulator = initial || 0; array.forEach (function (element) accumulator = callback (accumulator, array [i]);); retour accumulateur; ;
Deux choses à noter, ici:
accumulateur
au lieu de précédent
. C'est ce que vous verrez habituellement à l'état sauvage.accumulateur
une valeur initiale, si un utilisateur en fournit une, et par défaut à 0
, si non. Voici comment le réel réduire
se comporte aussi.À ce stade, vous pourriez ne pas être cette impressionné.
C'est suffisant: carte
, filtre
, et réduire
, seuls, ne sont pas terriblement intéressants.
Après tout, leur véritable pouvoir réside dans leur aptitude à la chaîne.
Disons que je veux faire ce qui suit:
Premièrement, définissons nos tâches pour lundi et mardi:
var monday = ['nom': 'écrire un tutoriel', 'durée': 180, 'nom': 'développement sur le web', 'durée': 120]; var mardi = ['nom': 'Continuez à écrire ce tutoriel', 'durée': 240, 'nom': 'Encore du développement web', 'durée': 180, 'nom':: Un ensemble beaucoup de rien ',' duration ': 240]; tâches var = = [lundi, mardi];
Et maintenant, notre belle transformation:
var result = tasks.reduce (function (accumulateur, actuel) return accumulator.concat (actuel);). map (fonction (tâche) return (task.duration / 60);). filter (fonction (durée) durée de retour> = 2;). carte (fonction (durée) durée de retour * 25;). réduction (fonction (accumulateur, courant) retour [(+ accumulateur) + (+ courant)];). map (function (dollar_amount) return '$' + dollar_amount.toFixed (2);).
Ou, plus concement:
// Concaténer notre tableau 2D en une liste unique var result = tasks.reduce ((acc, current) => acc.concat (current)) // Extrait la durée de la tâche et convertit les minutes en heures .map ((task) = > task.duration / 60) // Filtre toutes les tâches ayant pris moins de deux heures .filter ((duration) => duration> = 2) // Multiplie la durée de chaque tâche par notre tarif horaire .map ((duration) = > duration * 25) // Combinez les sommes en un montant en dollars .reduce ((acc, current) => [(+ acc) + (+ current)]) // Convertissez un montant en dollars "joliment imprimé". map ((montant) => '$' + montant.toFixé (2)) // Extrait le seul élément du tableau obtenu à partir de map .reduce ((formated_amount) => formated_amount);
Si vous en êtes arrivé là, cela devrait être assez simple. Il y a deux morceaux d'étrangeté à expliquer, bien que.
Premièrement, à la ligne 10, je dois écrire:
// Reste omis réduction (fonction (accumulateur, courant) return [(+ accumulateur) + + (+ current_];)
Deux choses à expliquer ici:
accumulateur
et actuel
contraindre leurs valeurs aux nombres. Si vous ne le faites pas, la valeur de retour sera la chaîne plutôt inutile, "12510075100"
.réduire
va cracher une seule valeur, ne pas un tableau. Cela finirait par jeter un Erreur-type
, parce que vous ne pouvez utiliser carte
sur un tableau! Le deuxième élément qui peut vous rendre un peu inconfortable est le dernier réduire
, à savoir:
// Carte restante omise (function (dollar_amount) return '$' + dollar_amount.toFixed (2);).
Cet appel à carte
renvoie un tableau contenant une seule valeur. Ici, nous appelons réduire
tirer cette valeur.
Pour ce faire, l’autre solution consiste à supprimer l’appel à réduire et à indexer dans le tableau carte
Crache:
var result = tasks.reduce (function (accumulateur, actuel) return accumulator.concat (actuel);). map (fonction (tâche) return (task.duration / 60);). filter (fonction (durée) durée de retour> = 2;). carte (fonction (durée) durée de retour * 25;). réduction (fonction (accumulateur, courant) retour [(+ accumulateur) + (+ courant)];). map (fonction (dollar_amount) return '$' + dollar_amount.toFixed (2);) [0];
C'est parfaitement correct. Si vous êtes plus à l'aise avec un tableau, allez-y.
Mais je vous encourage à ne pas. L'un des moyens les plus puissants d'utiliser ces fonctions est dans le domaine de la programmation réactive, où vous ne serez pas libre d'utiliser des index de tableaux. Abandonner cette habitude maintenant facilitera l'apprentissage des techniques réactives.
Enfin, voyons comment notre ami le pour chaque
la boucle le ferait:
var concatenated = lundi.concat (mardi), fees = [], formaté_sum, hourly_rate = 25, total_fee = 0; concatenated.forEach (fonction (tâche) var durée = tâche.duration / 60; si (durée> = 2) fees.push (duration * hourly_rate);); fees.forEach (fonction (fee) total_fee + = fee); var formated_sum = '$' + total_fee.toFixed (2);
Tolérable, mais bruyant.
Dans ce tutoriel, vous avez appris comment carte
, filtre
, et réduire
travail; comment les utiliser et à peu près comment ils sont mis en œuvre. Vous avez vu qu'ils vous permettent tous d'éviter l'état de mutation, qui en utilisant pour
et pour chaque
boucles nécessite, et vous devriez maintenant avoir une bonne idée de la façon de les chaîner tous ensemble.
À présent, je suis sûr que vous avez hâte de vous entraîner et de lire davantage. Voici mes trois principales suggestions pour savoir où aller ensuite:
JavaScript est devenu l'un des langages de facto du travail sur le Web. Ce n’est pas sans ses courbes d’apprentissage, et il existe de nombreux cadres et bibliothèques pour vous tenir occupé. Si vous recherchez des ressources supplémentaires à étudier ou à utiliser dans votre travail, consultez ce que nous avons à votre disposition sur le marché Envato..
Si vous voulez plus sur ce genre de chose, vérifiez mon profil de temps en temps; attrape-moi sur Twitter (@PelekeS); ou frapper mon blog à http://peleke.me.
Des questions, des commentaires ou des confusions? Laissez-les ci-dessous et je ferai de mon mieux pour revenir à chacun individuellement.
Apprendre JavaScript: le guide complet
Nous avons créé un guide complet pour vous aider à apprendre le JavaScript, que vous soyez débutant en tant que développeur Web ou que vous souhaitiez explorer des sujets plus avancés..