Une fonction est un bloc de code paramétrique défini une fois et appelé plusieurs fois par la suite. En JavaScript, une fonction est composée et influencée par de nombreux composants :
- Le code JavaScript qui forme le corps de la fonction
- La liste des paramètres
- Les variables accessibles depuis la portée lexicale
- La valeur retournée
- Le contexte
this
lorsque la fonction est invoquée - Une fonction nommée ou anonyme
- La variable qui contient l’objet de la fonction
-
arguments
objet (ou manquant dans une fonction flèche)
.
Ce billet vous apprend six approches pour déclarer des fonctions JavaScript : la syntaxe, les exemples et les pièges courants. De plus, vous comprendrez quand utiliser un type de fonction spécifique dans certaines circonstances.
1. Déclaration de fonction
Une déclaration de fonction est constituée du mot-clé
function
, suivi d’un nom de fonction obligatoire, une liste de paramètres dans une paire de parenthèses(para1, ..., paramN)
et une paire d’accolades{...}
qui délimite le corps du code.
Un exemple de déclaration de fonction :
// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
est une déclaration de fonction qui définit la fonction isEven
, qui détermine si un nombre est pair.
La déclaration de fonction crée une variable dans la portée actuelle avec l’identifiant égal au nom de la fonction. Cette variable contient l’objet fonction.
La variable fonction est hissée au sommet de la portée courante, ce qui signifie que la fonction peut être invoquée avant la déclaration (voir ce chapitre pour plus de détails).
La fonction créée est nommée, ce qui signifie que la propriété name
de l’objet fonction contient son nom. C’est utile pour visualiser la pile d’appels : en débogage ou en lecture de messages d’erreur.
Voyons ces propriétés dans un exemple :
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
La déclaration de fonction function hello(name) {...}
crée une variable hello
qui est hissée au sommet de la portée actuelle. La variable hello
contient l’objet fonction et hello.name
contient le nom de la fonction : 'hello'
.
1.1 Une fonction régulière
La déclaration de fonction correspond aux cas où une fonction régulière est nécessaire. Régulière signifie que vous déclarez la fonction une fois et que vous l’invoquez ensuite à de nombreux endroits différents. Voici le scénario de base :
function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10
Parce que la déclaration de fonction crée une variable dans la portée actuelle, parallèlement aux appels de fonction réguliers, elle est utile pour la récursion ou le détachement d’auditeurs d’événements. Contrairement aux expressions de fonctions ou aux fonctions flèches, qui ne créent pas de liaison avec la variable de la fonction par son nom.
Par exemple, pour calculer récursivement la factorielle, il faut accéder à la fonction à l’intérieur :
function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24
À l’intérieur de factorial()
un appel récursif est effectué en utilisant la variable qui détient la fonction : factorial(n - 1)
.
Il est possible d’utiliser une expression de fonction et de l’affecter à une variable régulière, par exemple const factorial = function(n) {...}
. Mais la déclaration de fonction function factorial(n)
est compacte (pas besoin de const
et =
).
Une propriété importante de la déclaration de fonction est son mécanisme de hissage. Il permet d’utiliser la fonction avant la déclaration dans la même portée.
Le hoisting est utile dans certaines situations. Par exemple, lorsque vous souhaitez voir comment la fonction est appelée au début d’un script, sans lire l’implémentation de la fonction. L’implémentation de la fonction peut se trouver plus bas dans le fichier, de sorte que vous ne ferez peut-être même pas défiler cet endroit.
Vous pouvez lire plus de détails sur le hissage de déclaration de fonction ici.
1.2 Différence avec l’expression de fonction
Il est facile de confondre la déclaration de fonction et l’expression de fonction. Elles se ressemblent beaucoup mais produisent des fonctions avec des propriétés différentes.
Une règle facile à retenir : la déclaration de fonction dans une déclaration commence toujours par le mot-clé function
. Sinon, il s’agit d’une expression de fonction (voir 2.).
L’exemple suivant est une déclaration de fonction où l’énoncé commence par le mot-clé function
:
// Function declaration: starts with "function"function isNil(value) { return value == null;}
Dans le cas des expressions de fonctions, la déclaration JavaScript ne commence pas par le mot-clé function
(il est présent quelque part au milieu du code de la déclaration) :
// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
1.3 Déclaration de fonction dans les conditionnelles
Certains environnements JavaScript peuvent lancer une erreur de référence lors de l’invocation d’une fonction dont la déclaration apparaît dans les blocs {...}
de if
for
ou while
.
Activons le mode strict et voyons ce qui se passe lorsqu’une fonction est déclarée dans une conditionnelle:
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
Lorsque l’on appelle ok()
, JavaScript lance ReferenceError: ok is not defined
, car la déclaration de la fonction se trouve à l’intérieur d’un bloc conditionnel.
La déclaration de fonction dans les conditionnels est autorisée en mode non strict, ce qui rend la situation encore plus confuse.
En règle générale pour ces situations, lorsqu’une fonction doit être créée par des conditions – utilisez une expression de fonction. Voyons comment cela est possible :
(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
Parce que la fonction est un objet régulier, affectez-la à une variable en fonction de la condition. L’invocation de ok()
fonctionne bien, sans erreur.
2. Expression de fonction
Une expression de fonction est déterminée par un
function
mot clé, suivi d’un nom de fonction facultatif, une liste de paramètres dans une paire de parenthèses(para1, ..., paramN)
et une paire d’accolades{ ... }
qui délimite le code du corps.
Quelques exemples de l’expression de fonction:
const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14
L’expression de fonction crée un objet fonction qui peut être utilisé dans différentes situations :
- Assigné à une variable comme un objet
count = function(...) {...}
- Créer une méthode sur un objet
sum: function() {...}
. - Utiliser la fonction comme callback
.reduce(function(...) {...})
L’expression de fonction est le cheval de bataille de JavaScript. Habituellement, vous traitez ce type de déclaration de fonction, aux côtés de la fonction flèche (si vous préférez la syntaxe courte et le contexte lexical).
2.1 Expression de fonction nommée
Une fonction est anonyme lorsqu’elle n’a pas de nom (name
la propriété est une chaîne vide ''
):
( function(variable) {return typeof variable; }).name; // => ''
C’est une fonction anonyme, dont le nom est une chaîne vide.
Parfois, le nom de la fonction peut être déduit. Par exemple, lorsque la fonction anonyme est affectée à une variable :
const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'
Le nom de la fonction anonyme est 'myFunctionVar'
, car myFunctionVar
le nom de la variable est utilisé pour déduire le nom de la fonction.
Lorsque l’expression a le nom spécifié, il s’agit d’une expression de fonction nommée. Elle possède quelques propriétés supplémentaires par rapport à l’expression de fonction simple :
- Une fonction nommée est créée, c’est-à-dire.
name
propriété contient le nom de la fonction - Dans le corps de la fonction, une variable avec le même nom contient l’objet de la fonction
Reprenons l’exemple ci-dessus, mais définissons un nom dans l’expression de fonction :
const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'
function funName(variable) {...}
est une expression de fonction nommée. La variable funName
est accessible dans la portée de la fonction, mais pas en dehors. Dans tous les cas, la propriété name
de l’objet fonction contient le nom : funName
.
2.2 Favoriser l’expression de fonction nommée
Lorsqu’une expression de fonction const fun = function() {}
est affectée à une variable, certains moteurs déduisent le nom de la fonction à partir de cette variable. Cependant, les callbacks pourraient être passés comme des expressions de fonction anonymes, sans stockage dans des variables : le moteur ne peut donc pas déterminer son nom.
Il est raisonnable de favoriser les fonctions nommées et d’éviter les fonctions anonymes pour obtenir des avantages tels que :
- Les messages d’erreur et les piles d’appels montrent des informations plus détaillées lors de l’utilisation des noms de fonctions
- Un débogage plus confortable en réduisant le nombre de noms de piles anonymes
- Le nom de la fonction dit ce qu’elle fait
- Vous pouvez accéder à la fonction à l’intérieur de sa portée pour les appels récursifs ou détacher les écouteurs d’événements
3. Définition de méthode abrégée
La définition de méthode abrégée peut être utilisée dans une déclaration de méthode sur les littéraux d’objet et les classes ES2015. Vous pouvez les définir en utilisant un nom de fonction, suivi d’une liste de paramètres dans une paire de parenthèses
(para1, ..., paramN)
et une paire d’accolades{ ... }
qui délimite les déclarations du corps.
L’exemple suivant utilise une définition de méthode raccourcie dans un littéral d’objet :
const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'
add()
et get()
méthodes de l’objet collection
sont définies à l’aide d’une définition de méthode abrégée. Ces méthodes sont appelées comme d’habitude : collection.add(...)
et collection.get(...)
.
L’approche courte de la définition de méthode présente plusieurs avantages par rapport à la définition traditionnelle de propriété avec un nom, deux points :
et une expression de fonction add: function(...) {...}
:
- Une syntaxe plus courte est plus facile à comprendre
- La définition de méthode abrégée crée une fonction nommée, contrairement à une expression de fonction. Elle est utile pour le débogage.
La class
syntaxe exige des déclarations de méthodes sous une forme courte :
class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'
3.1 Noms de propriétés et méthodes calculées
ECMAScript 2015 ajoute une fonctionnalité intéressante : les noms de propriétés calculées dans les littéraux d’objets et les classes.
Les propriétés calculées utilisent une syntaxe légèrement différente () {...}
, de sorte que la définition de la méthode se présente ainsi :
const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'
(...) {...}
et (...) {...}
sont des déclarations de méthodes raccourcies avec des noms de propriétés calculées.
4. Fonction flèche
Une fonction flèche est définie à l’aide d’une paire de parenthèses qui contient la liste des paramètres
(param1, param2, ..., paramN)
, suivie d’une flèche grasse=>
et d’une paire d’accolades{...}
qui délimite les déclarations du corps.
Lorsque la fonction flèche ne comporte qu’un seul paramètre, la paire de parenthèses peut être omise. Lorsqu’elle contient une seule déclaration, les accolades peuvent être omises également.
Voyons l’utilisation de base de la fonction flèche :
const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5
absValue
est une fonction flèche qui calcule la valeur absolue d’un nombre.
La fonction déclarée à l’aide d’une flèche grasse possède les propriétés suivantes :
- La fonction flèche ne crée pas son contexte d’exécution, mais le prend lexicalement (contrairement à l’expression de fonction ou à la déclaration de fonction, qui créent leur propre
this
en fonction de l’invocation) - La fonction flèche est anonyme. Cependant, le moteur peut déduire son nom à partir de la variable contenant la fonction.
-
arguments
objet n’est pas disponible dans la fonction flèche (contrairement aux autres types de déclaration qui fournissentarguments
objet). Vous êtes libre d’utiliser les paramètres de repos(...params)
, cependant.
4.1 Transparence du contexte
this
mot-clé est un aspect déroutant de JavaScript (consultez cet article pour une explication détaillée sur this
).
Parce que les fonctions créent leur propre contexte d’exécution, il est souvent difficile de détecter la valeur this
.
ECMAScript 2015 améliore l’utilisation de this
en introduisant la fonction flèche, qui prend le contexte lexicalement (ou utilise simplement this
de la portée externe immédiate). C’est bien parce que vous n’avez pas à utiliser .bind(this)
ou à stocker le contexte var self = this
lorsqu’une fonction a besoin du contexte englobant.
Voyons comment this
est hérité de la fonction externe :
class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // =>
Numbers
classe contient un tableau de chiffres et fournit une méthode addNumber()
pour insérer de nouveaux chiffres.
Lorsque addNumber()
est appelée sans arguments, une fermeture est retournée qui permet d’insérer des nombres. Cette fermeture est une fonction flèche qui a this
comme numbersObject
instance car le contexte est pris lexicalement de la méthode addNumbers()
.
Sans la fonction flèche, vous devez fixer manuellement le contexte. Cela signifie utiliser des solutions de contournement comme .bind()
method:
//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
ou stocker le contexte dans une variable séparée var self = this
:
//... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
La transparence du contexte peut être utilisée lorsque vous souhaitez conserver this
tel quel, pris dans le contexte englobant.
4.2 Rappels courts
Lors de la création d’une fonction flèche, les paires de parenthèses et les accolades sont facultatives pour un seul paramètre et une seule déclaration de corps. Cela aide à créer des fonctions de rappel très courtes.
Faisons une fonction qui trouve si un tableau contient 0
:
const numbers = ;numbers.some(item => item === 0); // => true
item => item === 0
est une fonction flèche qui a l’air simple.
Notez que les fonctions flèches courtes imbriquées sont difficiles à lire. La façon pratique d’utiliser la forme de fonction flèche la plus courte est un seul callback (sans imbrication).
Si nécessaire, utilisez la syntaxe étendue des fonctions flèches lorsque vous écrivez des fonctions flèches imbriquées. C’est tout simplement plus facile à lire.
5. Fonction générateur
La fonction générateur en JavaScript renvoie un objet Générateur. Sa syntaxe est similaire à celle de l’expression de fonction, de la déclaration de fonction ou de la déclaration de méthode, juste qu’elle nécessite un caractère étoile *
.
La fonction générateur peut être déclarée sous les formes suivantes :
a. Forme de déclaration de fonction function* <name>()
:
function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
b. Formulaire d’expression de fonction function* ()
:
const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
c. Forme de définition de méthode abrégée *<name>()
:
const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
Dans les 3 cas, la fonction générateur renvoie l’objet générateur g
. Plus tard, g
est utilisé pour générer des séries de nombres incrémentés.
6. Encore une chose : new Function
En JavaScript, les fonctions sont des objets de première classe – une fonction est un objet régulier de type function
.
Les manières de la déclaration décrites ci-dessus créent le même type d’objet fonction. Voyons un exemple :
function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true
Le type objet fonction possède un constructeur : Function
.
Lorsque Function
est invoqué comme constructeur new Function(arg1, arg2, ..., argN, bodyString)
, une nouvelle fonction est créée. Les arguments arg1, args2, ..., argN
passés au constructeur deviennent les noms des paramètres de la nouvelle fonction et le dernier argument bodyString
est utilisé comme code du corps de la fonction.
Créons une fonction qui additionne deux nombres :
const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
créée avec Function
constructeur. L’invocation a des paramètres numberA
et numberB
et le corps return numberA + numberB
.
Les fonctions créées de cette façon n’ont pas accès à la portée courante, donc les fermetures ne peuvent pas être créées. Elles sont toujours créées dans la portée globale.
Une application possible de new Function
est une meilleure façon d’accéder à l’objet global dans un navigateur ou un script NodeJS :
(function() { 'use strict'; const global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();
Rappellez-vous que les fonctions ne doivent presque jamais être déclarées à l’aide de new Function()
. Comme le corps de la fonction est évalué à l’exécution, cette approche hérite de nombreux problèmes d’utilisation de eval()
: risques de sécurité, débogage plus difficile, aucun moyen d’appliquer les optimisations du moteur, pas d’autocomplétion de l’éditeur.
7. Au final, quelle voie est la meilleure ?
Il n’y a pas de gagnant ou de perdant. La décision du type de déclaration à choisir dépend de la situation.
Il existe cependant quelques règles que vous pouvez suivre dans des situations courantes.
Si la fonction utilise this
de la fonction englobante, la fonction flèche est une bonne solution. Lorsque la fonction de rappel comporte une seule déclaration courte, la fonction flèche est également une bonne option, car elle crée un code court et léger.
Pour une syntaxe plus courte lors de la déclaration de méthodes sur des littéraux d’objet, la déclaration de méthode abrégée est préférable.
new Function
manière de déclarer les fonctions normalement ne devrait pas être utilisée. Principalement parce qu’elle ouvre des risques potentiels de sécurité, ne permet pas l’autocomplétion du code dans les éditeurs et perd les optimisations du moteur.
.