«Fonctions d'un ordre supérieur» est l'une de ces phrases souvent dispersées. Mais rarement quelqu'un peut-il s'arrêter et expliquer de quoi il s'agit. Vous savez peut-être déjà ce qu'on appelle des fonctions d'ordre supérieur. Mais comment les utilisons-nous dans de vrais projets? Quand et pourquoi sont-ils utiles? Pouvons-nous manipuler le DOM avec leur aide? Ou les gens qui utilisent ces fonctionnalités se montrent-ils simplement? Peut-être compliquent-ils insensément le code?
Je pensais que les fonctions d'ordre supérieur sont utiles. Maintenant, je les considère comme la propriété la plus importante de JavaScript en tant que langage. Mais avant d'en discuter, voyons d'abord ce que sont exactement les fonctions d'ordre supérieur. Et nous allons commencer avec des fonctions en tant que variables.
Fonctionne comme des objets de première classe
En JavaScript, il existe au moins trois façons (il y en a plus au total) d'écrire une nouvelle fonction. Tout d'abord, vous pouvez écrire
une déclaration de fonction :
J'espère que vous comprenez tout. De plus, vous savez probablement que vous pouvez écrire une
expression de fonction :
const itemise = function(el) { const li = document.createElement('li'); li.appendChild(el); return li; }
Et enfin, il existe une autre façon d'écrire la même fonction - comme une
fonction de flèche :
const itemise = (el) => { const li = document.createElement('li'); li.appendChild(el); return li; }
Dans ce cas, les trois méthodes sont équivalentes. Bien que cela ne se produise pas toujours, dans la pratique, chaque méthode présente de petites différences associées à ce qui arrive à la magie d'un mot-clé particulier et des étiquettes dans les traces de pile.
Mais notez que les deux derniers exemples affectent la fonction à une variable. Cela ressemble à une bagatelle. Pourquoi ne pas assigner une fonction à une variable? Mais c'est très important. Les fonctions en JavaScript appartiennent à la "
première classe ". Par conséquent, nous pouvons:
- Attribuez des fonctions aux variables.
- Passez des fonctions comme arguments à d'autres fonctions.
- Renvoie les fonctions d'autres fonctions.
C'est merveilleux, mais qu'est-ce que tout cela a à voir avec les fonctions d'ordre supérieur? Faites attention aux deux derniers points. Nous y reviendrons bientôt, mais pour l'instant, regardons quelques exemples.
Nous avons vu l'affectation de fonctions aux variables. Et si on les passait en paramètres? Écrivons une fonction qui peut être utilisée avec des éléments DOM. Si nous exécutons
document.querySelectorAll()
, en retour, nous n'obtenons pas un tableau, mais un
NodeList
.
NodeList
n'a pas de méthode
.map()
, comme les tableaux, nous écrivons donc ceci:
Ici, nous passons la fonction itemise comme argument à la fonction
elListMap
. Mais nous pouvons utiliser
elListMap
non seulement pour créer des listes. Par exemple, avec son aide, vous pouvez ajouter une classe à un ensemble d'éléments:
function addSpinnerClass(el) { el.classList.add('spinner'); return el; }
elLlistMap
prend une autre fonction en paramètre et convertit. Autrement dit, nous pouvons utiliser
elListMap
pour résoudre différents problèmes.
Nous avons examiné un exemple de passage de fonctions en tant que paramètres. Parlons maintenant du retour d'une fonction à partir d'une fonction. À quoi ça ressemble?
Tout d'abord, nous écrivons l'ancienne fonction habituelle. Nous devons prendre une liste d'éléments
li
et envelopper dans
ul
. Facile:
function wrapWithUl(children) { const ul = document.createElement('ul'); return [...children].reduce((listEl, child) => { listEl.appendChild(child); return listEl; }, ul); }
Et si alors nous avons un tas d'éléments de paragraphe que nous voulons envelopper dans un
div
? Pas de problème, nous allons écrire une fonction supplémentaire pour cela:
function wrapWithDiv(children) { const div = document.createElement('div'); return [...children].reduce((divEl, child) => { divEl.appendChild(child); return divEl; }, div); }
Cela fonctionne très bien. Cependant, ces deux fonctions sont très similaires, la seule différence réside dans l'élément parent que nous avons créé.
Maintenant, nous
pourrions écrire une fonction qui prend deux paramètres: le type de l'élément parent et la liste des éléments enfants. Mais il y a une autre option. Nous pouvons créer une fonction qui renvoie une fonction. Par exemple:
function createListWrapperFunction(elementType) {
Cela peut sembler un peu compliqué au début, alors divisons le code. Nous avons créé une fonction qui renvoie simplement une autre fonction. Mais cette fonction de retour se
souvient du paramètre
elementType
. Et puis, lorsque nous appelons la fonction retournée, il sait déjà quel élément créer. Par conséquent, vous pouvez créer
wrapWithUl
et
wrapWithDiv
:
const wrapWithUl = createListWrapperFunction('ul');
Cette astuce, lorsque la fonction retournée «se souvient» de quelque chose, est appelée
fermeture . Vous pouvez en savoir plus à leur sujet
ici . Les fermetures sont incroyablement pratiques, mais pour l'instant, nous n'y penserons pas.
Donc, nous avons trié:
- Assigner une fonction à une variable.
- Passer une fonction comme paramètre.
- Retourner une fonction d'une autre fonction ...
En général, les fonctions de la première classe sont agréables. Mais qu'en est-il de
la fonction d'ordre supérieur ? Regardons la définition.
Qu'est-ce qu'une fonction d'ordre supérieur?
Définition :
il s'agit d'une fonction qui prend une fonction en argument ou renvoie une fonction en résultat.Est-ce familier? En JavaScript, ce sont des fonctions de première classe. Autrement dit, les «fonctions d'ordre supérieur» ont exactement les mêmes avantages. En d'autres termes, c'est juste un nom fantaisiste pour une idée simple.
Exemples de fonctions d'ordre supérieur
Si vous commencez à chercher, alors vous commencez à remarquer des fonctions d'ordre supérieur partout. Les plus courantes sont les fonctions qui prennent d'autres fonctions comme paramètres.
Fonctions qui prennent d'autres fonctions comme paramètres
Lorsque vous passez un rappel, vous utilisez une fonction d'ordre supérieur. En développement front-end, on les retrouve partout. L'une des plus courantes est la méthode
.addEventListener()
. Nous l'utilisons lorsque nous voulons effectuer des actions en réponse à certains événements. Par exemple, je veux créer un bouton qui affiche un avertissement:
function showAlert() { alert('Fallacies do not cease to be fallacies because they become fashions'); } document.body.innerHTML += `<button type="button" class="js-alertbtn"> Show alert </button>`; const btn = document.querySelector('.js-alertbtn'); btn.addEventListener('click', showAlert);
Ici, nous avons créé une fonction qui affiche un avertissement, ajouté un bouton à la page et passé la fonction
showAlert()
comme argument à
btn.addEventListener()
.
Nous rencontrons également des fonctions d'ordre supérieur lorsque nous utilisons des
méthodes d'itération de tableau : par exemple,
.map()
,
.filter()
et
.reduce()
. Comme dans la fonction
elListMap()
:
function elListMap(transform, list) { return [...list].map(transform); }
Les fonctions d'ordre supérieur aident également à gérer les retards et le timing. Les fonctions
setTimeout()
et
setInterval()
aident à contrôler le
moment où les fonctions sont exécutées. Par exemple, si vous devez supprimer la classe de
highlight
après 30 secondes, vous pouvez le faire comme ceci:
function removeHighlights() { const highlightedElements = document.querySelectorAll('.highlighted'); elListMap(el => el.classList.remove('highlighted'), highlightedElements); } setTimeout(removeHighlights, 30000);
Encore une fois, nous avons créé une fonction et l'avons passée à une autre fonction comme argument.
Comme vous pouvez le voir, JavaScript a souvent des fonctions qui acceptent d'autres fonctions. Et vous les utilisez probablement déjà.
Fonctions Retourner des fonctions
Les fonctions de ce type ne sont pas retrouvées aussi souvent que les précédentes. Mais ils sont également utiles. L'un des meilleurs exemples est la fonction
peut -
être () . J'ai adapté une variante du
livre JavaScript Allongé :
function maybe(fn) return function _maybe(...args) {
Au lieu de comprendre le code, voyons d'abord comment il peut être appliqué. Examinons à
elListMap()
fonction
elListMap()
:
Que se passe-t-il si je passe accidentellement null ou une valeur non définie à
elListMap()
? Nous obtiendrons une TypeError et une chute de l'opération en cours, quelle qu'elle soit. Cela peut être évité avec la fonction
maybe()
-
maybe()
:
const safeElListMap = maybe(elListMap); safeElListMap(x => x, null);
Au lieu de tomber, la fonction retournera
undefined
. Et si nous passions cela à une autre fonction protégée par
maybe()
, alors nous serions à nouveau
undefined
.
maybe()
peut protéger un certain nombre de fonctions, il est beaucoup plus facile d'écrire un milliard d'
if
.
Les fonctions qui renvoient des fonctions sont également courantes dans le monde React. Par exemple,
connect()
.
Et maintenant?
Nous avons vu plusieurs exemples d'utilisation de fonctions d'ordre supérieur. Et maintenant? Que peuvent-ils nous donner ce que nous ne pourrions pas obtenir sans eux?
Pour répondre à cette question, regardons un autre exemple - la méthode de tableau
.sort()
. Oui, il a des défauts. Il change le tableau au lieu d'en renvoyer un nouveau. Mais oublions ça pour l'instant. La méthode
.sort()
est une fonction d'ordre supérieur; elle prend une autre fonction comme l'un des paramètres.
Comment ça marche? Si nous voulons trier un tableau de nombres, nous devons d'abord créer une fonction de comparaison:
function compareNumbers(a, b) { if (a === b) return 0; if (a > b) return 1; return -1; }
Triez maintenant le tableau:
let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2]; nums.sort(compareNumbers); console.log(nums);
Vous pouvez trier des listes de nombres. Mais à quoi ça sert? À quelle fréquence avons-nous une liste de numéros à trier? Pas souvent. Habituellement, je dois trier un tableau d'objets:
let typeaheadMatches = [ { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'bog', weight: 0.5, matchedChars: ['bog'], }, { keyword: 'boggle', weight: 0.3, matchedChars: ['bog'], }, { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'toboggan', weight: 0.15, matchedChars: ['bog'], }, { keyword: 'bag', weight: 0.1, matchedChars: ['b', 'g'], } ];
Supposons que je veuille trier ce tableau en fonction du poids de chaque enregistrement. Je
pourrais écrire une nouvelle fonction de tri à partir de zéro. Mais pourquoi, si vous pouvez créer une nouvelle fonction de comparaison:
function compareTypeaheadResult(word1, word2) { return -1 * compareNumbers(word1.weight, word2.weight); } typeaheadMatches.sort(compareTypeaheadResult); console.log(typeaheadMatches);
Vous pouvez écrire une fonction de comparaison pour tout type de tableau. La méthode
.sort()
nous aide: «Si vous me donnez une fonction de comparaison, je vais trier n'importe quel tableau. Ne vous inquiétez pas de son contenu. Si vous donnez une fonction de tri, je la trie. » Par conséquent, nous n'avons pas besoin d'écrire un algorithme de tri par nous-mêmes, nous nous concentrerons sur la tâche beaucoup plus simple de comparer deux éléments.
Imaginez maintenant que nous n'utilisons pas de fonctions d'ordre supérieur. Nous ne pouvons pas passer une fonction à la méthode
.sort()
. Nous devrons écrire une nouvelle fonction de tri chaque fois que nous aurons besoin de trier un tableau d'un type différent. Ou vous devez réinventer la même chose avec des pointeurs de fonction ou des objets. Dans tous les cas, cela se révélera très maladroitement.
Cependant, nous avons des fonctions d'ordre supérieur qui nous permettent de séparer la fonction de tri de la fonction de comparaison. Supposons qu'un développeur de navigateur intelligent ait mis à jour
.sort()
pour utiliser un algorithme plus rapide. Ensuite, votre code ne fera que gagner, quel que soit le contenu des tableaux triables. Et ce schéma est vrai pour
tout un ensemble de fonctions de tableaux d'ordre supérieur .
Cela nous amène à une telle idée. La méthode
.sort()
extrait la tâche de
tri du
contenu du tableau. C'est ce qu'on appelle la séparation des préoccupations. Les fonctions d'ordre supérieur vous permettent de créer des abstractions qui, sans elles, seraient très lourdes, voire impossibles. Et la création d'abstractions représente 80% du travail des ingénieurs logiciels.
Lorsque nous refactorisons le code pour supprimer les répétitions, nous créons des abstractions. Nous voyons le motif et le remplaçons par une représentation abstraite. En conséquence, le code devient plus significatif et plus facile à comprendre. C'est du moins le but.
Les fonctions d'ordre supérieur sont un outil puissant pour créer des abstractions. Et avec les abstractions, toute une branche des mathématiques, la théorie des catégories, est associée. Plus précisément, la théorie des catégories est consacrée à la recherche d'abstractions d'abstractions. En d'autres termes, nous parlons de trouver des modèles de modèles. Et au cours des 70 dernières années, des programmeurs intelligents ont emprunté beaucoup d'idées à partir de là, qui se sont transformées en propriétés de langages et de bibliothèques. Si nous apprenons ces modèles, nous pouvons parfois remplacer de gros morceaux de code. Ou simplifiez des problèmes complexes grâce à d'élégantes combinaisons de blocs de construction simples. Ces blocs sont des fonctions d'ordre supérieur. Par conséquent, ils sont si importants qu'ils nous donnent un outil puissant pour lutter contre la complexité de notre code.
Documents supplémentaires sur les fonctions d'ordre supérieur:
Vous utilisez probablement déjà des fonctions d'ordre supérieur. C'est tellement simple en JavaScript que nous n'y pensons même pas. Mais il vaut mieux savoir de quoi les gens parlent quand ils disent cette phrase. Ce n'est pas difficile. Mais derrière une idée simple, il y a beaucoup de pouvoir.
Si vous êtes expérimenté dans la programmation fonctionnelle, vous remarquerez peut-être que je n'ai pas utilisé de fonctions pures et quelques noms de fonctions verbeux. Ce n'est pas parce que je n'ai pas entendu parler des fonctions impures ou des principes généraux de la programmation fonctionnelle. Et je n'écris pas un tel code en production. J'ai essayé de prendre des exemples pratiques qui seraient clairs pour les débutants. Parfois, je devais faire des compromis. Si je suis intéressé, j'ai déjà écrit sur la
propreté fonctionnelle et les
principes généraux de la programmation fonctionnelle .