Fonctions flèches ES6 : Syntaxe grasse et concise en JavaScript

Dans cet article, vous apprendrez tout sur la syntaxe des fonctions flèches de JavaScript – y compris certains des gotchas dont vous devez être conscient lorsque vous exploitez les fonctions flèches dans votre code. Vous verrez de nombreux exemples qui illustrent leur fonctionnement.

Les fonctions flèches ont été introduites dans JavaScript avec la sortie de l’ECMAScript 2015, également connu sous le nom de ES6. Leur syntaxe concise, et la façon dont elles gèrent le mot-clé this, font partie des principales caractéristiques qui ont contribué au succès considérable des fonctions flèches auprès des développeurs.

Transformation d’une fonction pré-ES6 en une fonction flèche

Vous pourriez considérer les fonctions comme une sorte de recette où vous stockez des instructions utiles pour accomplir quelque chose dont vous avez besoin dans votre programme, comme effectuer une action ou retourner une valeur. En appelant votre fonction, vous exécutez les étapes incluses dans votre recette, et vous pouvez le faire chaque fois que vous appelez cette fonction sans avoir besoin de réécrire la recette encore et encore.

Voici une façon standard de déclarer une fonction puis de l’appeler en JavaScript :

// function declarationfunction sayHiStranger() { return 'Hi, stranger!'}// call the functionsayHiStranger()

Vous pouvez également écrire la même fonction comme une expression de fonction, comme ceci :

const sayHiStranger = function () { return 'Hi, stranger!'}

Les fonctions flèches sont toujours des expressions. Voici comment vous pourriez réécrire la fonction ci-dessus en utilisant la notation flèche grasse :

const sayHiStranger = () => 'Hi, stranger'

Les avantages de cette notation comprennent :

  • une seule ligne de code
  • pas de mot-clé function
  • pas de mot-clé return
  • pas d’accolades {}

En JavaScript, les fonctions sont des « citoyens de première classe ». C’est-à-dire que vous pouvez stocker des fonctions dans des variables, les passer à d’autres fonctions en tant qu’arguments, et les retourner d’autres fonctions en tant que valeurs. Vous pouvez faire tout cela en utilisant des fonctions flèches.

Passons en revue les différentes façons dont vous pouvez écrire les fonctions flèches.

La syntaxe sans parens

Dans l’exemple ci-dessus, la fonction n’a pas de paramètres. Dans ce cas, vous devez ajouter un ensemble de parenthèses vides () avant le symbole de la flèche grasse (=>). Il en va de même lorsque vous avez plus d’un paramètre :

const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`// call the functionconsole.log(getNetflixSeries('Bridgerton', '2020') )// output: The Bridgerton series was released in 2020

Avec un seul paramètre, cependant, vous pouvez aller de l’avant et laisser de côté les parenthèses (vous n’êtes pas obligé, mais vous pouvez) :

const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"// call the functionconsole.log(favoriteSeries("Bridgerton"))// output: "Let's watch it"

Faites attention, cependant. Si, par exemple, vous décidez d’utiliser un paramètre par défaut, vous devez l’envelopper entre parenthèses :

// with parentheses: correctconst bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`// outputs: "Bridgerton is the best"console.log(bestNetflixSeries())// no parentheses: errorconst bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)

Et ce n’est pas parce que vous pouvez, que vous devez. Mélangé avec un peu de sarcasme léger et bien intentionné, Kyle Simpson (de la célébrité de You Don’t Know JS) a mis ses pensées sur l’omission des parenthèses dans cet organigramme.

Implicit Return

Lorsque vous n’avez qu’une seule expression dans le corps de votre fonction, vous pouvez tout garder sur une ligne, supprimer les accolades et vous débarrasser du mot-clé return. Vous venez de voir comment ces jolies lignes uniques fonctionnent dans les exemples ci-dessus. Voici un autre exemple, juste pour faire bonne mesure. La fonction orderByLikes() fait ce qu’elle dit sur l’étain : c’est-à-dire qu’elle renvoie un tableau d’objets de séries Netflix classés par le plus grand nombre de likes :

// using the JS sort() function to sort the titles in descending order // according to the number of likes (more likes at the top, fewer at the bottomconst orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )// call the function // output:the titles and the n. of likes in descending orderconsole.log(orderByLikes)

C’est cool, mais gardez un œil sur la lisibilité de votre code – surtout lorsque vous enchaînez un tas de fonctions fléchées en utilisant des one-liners et la syntaxe sans parenthèses, comme dans cet exemple :

const greeter = greeting => name => `${greeting}, ${name}!`

Qu’est-ce qui se passe ici ? Essayez d’utiliser la syntaxe de fonction régulière :

function greeter(greeting) { return function(name) { return `${greeting}, ${name}!` }} 

Maintenant, vous pouvez rapidement voir comment la fonction externe greeter a un paramètre, greeting, et renvoie une fonction anonyme. Cette fonction interne a à son tour un paramètre appelé name et renvoie une chaîne de caractères en utilisant la valeur de greeting et name. Voici comment vous pouvez appeler la fonction :

const myGreet = greeter('Good morning')console.log( myGreet('Mary') ) // output: "Good morning, Mary!" 

Watch Out for these Implicit Return Gotchas

Lorsque votre fonction flèche contient plus d’une déclaration, vous devez toutes les envelopper dans des accolades et utiliser le mot-clé return. Dans le code ci-dessous, la fonction construit un objet contenant le titre et le résumé de quelques séries Netflix (les critiques Netflix proviennent du site Rotten Tomatoes) :

const seriesList = netflixSeries.map( series => { const container = {} container.title = series.name container.summary = series.summary // explicit return return container} )

La fonction flèche à l’intérieur de la fonction .map() se développe sur une série d’énoncés, à la fin desquels elle renvoie un objet. Cela rend inévitable l’utilisation d’accolades autour du corps de la fonction. De plus, comme vous utilisez des accolades, un retour implicite n’est pas une option. Vous devez utiliser le mot-clé return.

Si votre fonction flèche renvoie un littéral objet en utilisant le retour implicite, vous devez envelopper l’objet à l’intérieur de parenthèses rondes. Ne pas le faire entraînera une erreur, car le moteur JS analyse par erreur les accolades du littéral objet comme les accolades de la fonction. Et comme vous venez de le remarquer ci-dessus, lorsque vous utilisez des accolades dans une fonction fléchée, vous ne pouvez pas omettre le mot-clé return.

Cette syntaxe est démontrée dans la version plus courte du code précédent :

// Uncaught SyntaxError: unexpected token: ':'const seriesList = netflixSeries.map(series => { title: series.name });// Works fineconst seriesList = netflixSeries.map(series => ({ title: series.name }));

Vous ne pouvez pas nommer les fonctions flèches

Les fonctions qui n’ont pas d’identifiant de nom entre le mot-clé function et la liste des paramètres sont appelées fonctions anonymes. Voici à quoi ressemble une expression régulière de fonction anonyme :

const anonymous = function() { return 'You can\'t identify me!' }

Les fonctions fléchées sont toutes des fonctions anonymes :

const anonymousArrowFunc = () => 'You can\'t identify me!' 

Depuis ES6, les variables et les méthodes peuvent déduire le nom d’une fonction anonyme à partir de sa position syntaxique, en utilisant sa propriété name. Cela permet d’identifier la fonction lors de l’inspection de sa valeur ou du signalement d’une erreur.

Vérifiez cela en utilisant anonymousArrowFunc :

console.log(anonymousArrowFunc.name)// output: "anonymousArrowFunc"

Mais sachez que cette propriété name inférée n’existe que lorsque la fonction anonyme est affectée à une variable, comme dans les exemples ci-dessus. Si vous utilisez une fonction anonyme comme callback, par exemple, vous perdez cette fonctionnalité utile. Ceci est illustré dans la démonstration ci-dessous où la fonction anonyme à l’intérieur de la méthode .setInterval() ne peut pas se prévaloir de la propriété name :

let counter = 5let countDown = setInterval(() => { console.log(counter) counter-- if (counter === 0) { console.log("I have no name!!") clearInterval(countDown) }}, 1000)

Et ce n’est pas tout. Cette propriété name déduite ne fonctionne toujours pas comme un identifiant approprié que vous pouvez utiliser pour faire référence à la fonction depuis l’intérieur d’elle-même – comme pour la récursion, les événements de déliaison, etc.

L’anonymat intrinsèque des fonctions flèches a conduit Kyle Simpson à exprimer son point de vue sur les fonctions flèches comme suit :

Puisque je ne pense pas que les fonctions anonymes soient une bonne idée à utiliser fréquemment dans vos programmes, je ne suis pas un fan de l’utilisation de la forme => de la fonction flèche. – Vous ne connaissez pas JS

Comment les fonctions flèches traitent le mot-clé this

La chose la plus importante à retenir au sujet des fonctions flèches est la façon dont elles traitent le mot-clé this. En particulier, le mot-clé this à l’intérieur d’une fonction flèche ne se rebondit pas.

Pour illustrer ce que cela signifie, consultez la démo ci-dessous :

Voyez le Pen
JS this in arrow functions par SitePoint (@SitePoint)
sur CodePen.

Voici un bouton. Cliquer sur le bouton déclenche un compteur inversé de 5 à 1. Les chiffres sont affichés sur le bouton lui-même :

<button class="start-btn">Start Counter</button>...const startBtn = document.querySelector(".start-btn");startBtn.addEventListener('click', function() { this.classList.add('counting') let counter = 5; const timer = setInterval(() => { this.textContent = counter counter -- if(counter < 0) { this.textContent = 'THE END!' this.classList.remove('counting') clearInterval(timer) } }, 1000) })

Notez comment le gestionnaire d’événement à l’intérieur de la méthode .addEventListener() est une expression de fonction anonyme régulière, et non une fonction flèche. Pourquoi ? Si vous enregistrez this à l’intérieur de la fonction, vous verrez qu’elle référence l’élément bouton auquel l’écouteur a été attaché, ce qui est exactement ce qui est attendu et ce qui est nécessaire pour que le programme fonctionne comme prévu :

startBtn.addEventListener('click', function() { console.log(this) ...})

Voici à quoi cela ressemble dans la console des outils de développement de Firefox :

Cependant, essayez de remplacer la fonction régulière par une fonction flèche, comme ceci :

startBtn.addEventListener('click', () => { console.log(this) ...})

Maintenant, this ne fait plus référence au bouton. Au lieu de cela, il fait référence à l’objet Window :

Cela signifie que, si vous voulez utiliser this pour ajouter une classe au bouton après qu’il ait été cliqué, par exemple, votre code ne fonctionnera pas :

// change button's border's appearancethis.classList.add('counting')

Voici le message d’erreur dans la console :

Cela se produit parce que, lorsque vous utilisez une fonction flèche, la valeur du mot clé this n’est pas rebondie, mais elle est héritée de la portée du parent (c’est ce qu’on appelle la portée lexicale). Dans ce cas particulier, la fonction flèche en question est passée comme un argument à la méthode startBtn.addEventListener(), qui est dans la portée globale. Par conséquent, le this à l’intérieur du gestionnaire de la fonction flèche est également lié à la portée globale – c’est-à-dire à l’objet Window.

Donc, si vous voulez que this fasse référence au bouton de démarrage dans le programme, l’approche correcte est d’utiliser une fonction régulière, pas une fonction flèche.

La prochaine chose à remarquer dans la démo ci-dessus est le code à l’intérieur de la méthode .setInterval(). Ici aussi, vous trouverez une fonction anonyme, mais cette fois, c’est une fonction flèche. Pourquoi ?

Notez quelle serait la valeur de this si vous utilisiez une fonction régulière :

const timer = setInterval(function() { console.log(this) ...}, 1000)

Serait-ce l’élément button ? Pas du tout. Ce serait l’objet Window !

En fait, le contexte a changé, puisque this est maintenant à l’intérieur d’une fonction non liée ou globale qui est passée comme argument à .setInterval(). Par conséquent, la valeur du mot-clé this a également changé, car il est maintenant lié à la portée globale. Un hack commun dans cette situation a été celui d’inclure une autre variable pour stocker la valeur du mot-clé this afin qu’il continue à faire référence à l’élément attendu – dans ce cas, l’élément button:

const that = thisconst timer = setInterval(function() { console.log(that) ...}, 1000)

Vous pouvez également utiliser .bind() pour résoudre le problème :

const timer = setInterval(function() { console.log(this) ...}.bind(this), 1000)

Avec les fonctions flèches, le problème disparaît complètement. Voici quelle est la valeur de this lorsque vous utilisez une fonction flèche :

const timer = setInterval( () => { console.log(this) ...}, 1000)

Cette fois, la console enregistre le bouton, ce qui est exactement ce dont on a besoin. En fait, le programme va changer le texte du bouton, il a donc besoin de this pour faire référence à l’élément button:

const timer = setInterval( () => { console.log(this) // the button's text displays the timer value this.textContent = counter}, 1000)

Les fonctions flèches n’ont pas leur propre contexte this. Elles héritent de la valeur de this du parent, et c’est grâce à cette caractéristique qu’elles constituent un excellent choix dans des situations comme celle ci-dessus.

Les fonctions flèches ne sont pas toujours le bon outil pour le travail

Les fonctions flèches ne sont pas seulement une nouvelle façon fantaisiste d’écrire des fonctions en JavaScript. Elles ont leurs propres bizarreries et limites, ce qui signifie qu’il y a des cas où vous ne voulez pas utiliser une fonction flèche. Le gestionnaire de clics de la démo précédente en est un bon exemple, mais ce n’est pas le seul. Examinons-en quelques autres.

Fonctions flèches en tant que méthodes sur des objets

Les fonctions flèches ne fonctionnent pas bien en tant que méthodes sur des objets. En voici un exemple. Considérez cet objet netflixSeries, qui a quelques propriétés et un couple de méthodes. L’appel de console.log(netflixSeries.getLikes()) devrait imprimer un message avec le nombre actuel de likes, et l’appel de console.log(netflixSeries.addLike()) devrait augmenter le nombre de likes d’une unité, puis imprimer la nouvelle valeur avec un message de remerciement sur la console :

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes: () => `${this.title} has ${this.likes} likes`, addLike: () => { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }

Au lieu de cela, l’appel de la méthode .getLikes() renvoie « undefined has NaN likes », et l’appel de la méthode .addLike() renvoie « Thank you for liking undefined, which now has NaN likes ». Donc, this.title et this.likes ne parviennent pas à référencer les propriétés de l’objet title et likes respectivement.

Une fois encore, le problème réside dans le cadrage lexical des fonctions flèches. Le this à l’intérieur de la méthode de l’objet fait référence à la portée du parent, qui dans ce cas est l’objet Window, pas le parent lui-même – c’est-à-dire pas l’objet netflixSeries.

La solution, bien sûr, est d’utiliser une fonction régulière :

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes() { return `${this.title} has ${this.likes} likes` }, addLike() { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }// call the methods console.log(netflixSeries.getLikes())console.log(netflixSeries.addLike())// output: After Life has 5 likesThank you for liking After Life, which now has 6 likes

Fonctions fléchées avec des bibliothèques tierces

Un autre gotcha à connaître est que les bibliothèques tierces lieront souvent les appels de méthode de sorte que la valeur this pointe vers quelque chose d’utile.

Par exemple, à l’intérieur d’un gestionnaire d’événements jQuery, this vous donnera accès à l’élément DOM auquel le gestionnaire a été lié :

$('body').on('click', function() { console.log(this)})// <body>

Mais si nous utilisons une fonction flèche – qui, comme nous l’avons vu, n’a pas son propre contexte this – nous obtenons des résultats inattendus :

$('body').on('click', () =>{ console.log(this)})// Window

Voici un autre exemple utilisant Vue.js:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// Hello, World!

Dans le crochet created, this est lié à l’instance Vue, de sorte que le message « Hello, World ! » s’affiche.

Si nous utilisons une fonction flèche, cependant, this pointera vers la portée parent, qui n’a pas de propriété message:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// undefined

Les fonctions flèche n’ont pas d’arguments Objet

Parfois, vous pourriez avoir besoin de créer une fonction avec un nombre indéfini de paramètres. Par exemple, disons que vous voulez créer une fonction qui répertorie vos séries Netflix préférées classées par préférence. Cependant, vous ne savez pas encore combien de séries vous allez inclure. JavaScript met à disposition l’objet arguments, un objet semblable à un tableau (pas un tableau à part entière, cependant), qui stocke les valeurs qui sont passées à la fonction lorsqu’elle est appelée.

Tentez d’implémenter cette fonctionnalité en utilisant une fonction flèche :

const listYourFavNetflixSeries = () => { // we need to turn the arguments into a real array // so we can use .map() const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments)}console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life')) 

Lorsque vous appelez la fonction, vous obtenez le message d’erreur suivant : Uncaught ReferenceError: arguments is not defined. Ce que cela signifie, c’est que l’objet arguments n’est pas disponible à l’intérieur des fonctions flèches. En fait, le remplacement de la fonction flèche par une fonction ordinaire fait l’affaire :

const listYourFavNetflixSeries = function() { const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments) }console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))// output: 

Donc, si vous avez besoin de l’objet arguments, vous ne pouvez pas utiliser les fonctions flèches.

Mais que faire si vous voulez vraiment utiliser une fonction flèche pour reproduire la même fonctionnalité ? Une chose que vous pouvez faire est d’utiliser les paramètres de repos ES6 (...). Voici comment vous pourriez réécrire votre fonction :

const listYourFavNetflixSeries = (...seriesList) => { return seriesList.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) }

Conclusion

En utilisant les fonctions flèche, vous pouvez écrire des one-liners concis avec retour implicite et enfin oublier les hacks de l’ancien temps pour résoudre la liaison du mot-clé this en JavaScript. Les fonctions flèche fonctionnent également très bien avec les méthodes de tableau comme .map(), .sort(), .forEach(), .filter() et .reduce(). Mais n’oubliez pas que les fonctions flèches ne remplacent pas les fonctions JS ordinaires et qu’elles ne peuvent pas être utilisées pour tout.

Si vous avez des questions sur les fonctions flèches, ou si vous avez besoin d’aide pour les utiliser correctement, je vous recommande de vous arrêter sur les forums conviviaux de SitePoint, où il y a beaucoup de programmeurs compétents prêts à vous aider.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.