Chapitre 416 minDébutant
Fonctions et fonctions fléchées en JavaScript
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- déclarer une fonction et une fonction fléchée ;
- utiliser les paramètres par défaut, le reste et la décomposition ;
- expliquer la portée d'une variable et ce qu'est une closure ;
- passer une fonction en argument (callback) ;
- écrire des fonctions pures, faciles à tester.
4 exercices corrigés
Une fonction JavaScript est un bloc de code nommé qui reçoit des paramètres, fait un traitement et renvoie un résultat avec return ; elle peut s'écrire avec le mot-clé function ou sous forme de fonction fléchée (x) => …. Ce chapitre présente les façons de déclarer une fonction, les paramètres, la portée des variables, les closures et les callbacks, puis ce qui rend une fonction facile à tester. La notion de fonction elle-même est expliquée dans le chapitre Fonctions et procédures du cours d'algorithmique.
Déclarer une fonction
function valeurPiece(piece) {
return piece.quantite * piece.prix;
}
const ssd = { ref: 'SSD-512', quantite: 12, prix: 42.9 };
console.log(valeurPiece(ssd)); // 514.8
valeurPieceest le nom : un verbe ou un groupe qui dit ce que fait la fonction ou ce qu'elle calcule.pieceest un paramètre : une variable locale qui reçoit la valeur passée à l'appel, l'argument (ssd).returnrenvoie le résultat et arrête la fonction : les lignes qui suivent unreturnexécuté ne sont jamais lues.
Une fonction sans return (ou avec un return seul) renvoie undefined. C'est une erreur fréquente : écrire le calcul, oublier le return, et obtenir undefined à l'appel.
JavaScript ne vérifie pas le nombre d'arguments. Un argument manquant vaut undefined ; un argument en trop est ignoré :
function deux(a, b) {
return [a, b];
}
deux(1); // [1, undefined]
deux(1, 2, 3); // [1, 2]
Expression de fonction et fonction fléchée
Une fonction est une valeur comme une autre : on peut la ranger dans une variable. Trois écritures donnent le même résultat ici :
// 1. Déclaration de fonction
function valeurPiece(piece) {
return piece.quantite * piece.prix;
}
// 2. Expression de fonction
const valeurPiece2 = function (piece) {
return piece.quantite * piece.prix;
};
// 3. Fonction fléchée (arrow function)
const valeurPiece3 = (piece) => piece.quantite * piece.prix;
La fonction fléchée, apparue en 2015, est l'écriture la plus courte. Ses règles :
| Écriture | Sens |
|---|---|
(a, b) => a + b | Corps d'une seule expression : sa valeur est renvoyée sans return |
p => p.ref | Un seul paramètre : les parenthèses sont facultatives |
() => Date.now() | Aucun paramètre : parenthèses vides obligatoires |
(p) => { const v = p.quantite * p.prix; return v; } | Corps entre accolades : return obligatoire |
(p) => ({ ref: p.ref, valeur: p.prix }) | Renvoyer un objet : parenthèses autour des accolades |
Quelle écriture choisir ? Les fonctions fléchées sont idéales pour les fonctions courtes passées en argument (pieces.filter(p => p.quantite === 0)). Pour les fonctions principales d'un fichier, la déclaration function se lit bien et peut être appelée avant sa ligne de définition (voir plus bas). Une fonction fléchée n'a pas son propre this : ne l'utilise pas comme méthode d'un objet qui a besoin de this (chapitre 6).
Paramètres par défaut
Un paramètre peut recevoir une valeur par défaut, utilisée quand l'argument est absent ou vaut undefined :
function prixTTC(prixHT, taux = 0.2) {
return prixHT * (1 + taux);
}
prixTTC(100); // 120
prixTTC(100, 0.055); // 105.5
prixTTC(100, null); // 100 : null n'est pas undefined, la valeur par défaut ne s'applique pas
Paramètres du reste et décomposition
Le paramètre du reste ...nom regroupe tous les arguments restants dans un tableau. Il est toujours le dernier :
function total(...montants) {
let somme = 0;
for (const m of montants) somme += m;
return somme;
}
total(42.9, 38.5); // 81.4
total(); // 0
Quand une fonction attend plusieurs informations, passe-lui plutôt un objet et décompose-le dans les paramètres. L'appel devient lisible et l'ordre ne compte plus :
function etiquette({ ref, nom, quantite = 0 }) {
return `${ref} – ${nom} (${quantite})`;
}
etiquette({ ref: 'PAT-01', nom: 'Pâte thermique' }); // 'PAT-01 – Pâte thermique (0)'
La décomposition (destructuring) est détaillée aux chapitres 5 et 6.
La portée des variables
Une variable déclarée avec let ou const n'existe que dans le bloc { } où elle est déclarée, et dans les blocs qu'il contient. C'est sa portée (scope). Une variable interne peut porter le même nom qu'une variable externe : elle la masque.
const x = 'global';
function portee() {
const x = 'locale';
if (true) {
const x = 'bloc';
console.log(x); // 'bloc'
}
console.log(x); // 'locale'
}
portee();
console.log(x); // 'global'
Une fonction voit les variables du code qui l'entoure, mais l'inverse est faux : rien ne sort d'une fonction, sauf ce qu'elle renvoie. Limite les variables globales : n'importe quelle partie du code peut les modifier, et un bug devient difficile à localiser.
Les déclarations de fonction sont « remontées » (hoisting) : tu peux appeler une fonction déclarée avec function avant sa ligne dans le fichier. Une fonction rangée dans une const ne l'est pas : l'appeler avant sa définition provoque ReferenceError: Cannot access 'nom' before initialization.
Les closures
Une closure (fermeture) est une fonction qui garde l'accès aux variables de l'endroit où elle a été créée, même après la fin de la fonction qui les contenait :
function creerCompteur() {
let n = 0;
return () => {
n++;
return n;
};
}
const numeroBon = creerCompteur();
const numeroDevis = creerCompteur();
numeroBon(); // 1
numeroBon(); // 2
numeroDevis(); // 1 : chaque compteur a son propre n
numeroBon(); // 3
creerCompteur est terminée, pourtant la fonction renvoyée se souvient de n. Personne d'autre ne peut modifier n : la closure protège la donnée. Tu utilises des closures sans y penser chaque fois qu'un écouteur d'événement (chapitre 8) lit une variable déclarée autour de lui.
Les callbacks : passer une fonction en argument
Puisqu'une fonction est une valeur, on peut la passer à une autre fonction. La fonction passée s'appelle un callback (fonction de rappel) : l'autre fonction la « rappellera » au bon moment.
function pourChaque(liste, action) {
for (const element of liste) {
action(element);
}
}
pourChaque(['SSD-512', 'RAM-16'], (ref) => console.log(`Pièce ${ref}`));
// Pièce SSD-512
// Pièce RAM-16
C'est exactement ce que font les méthodes de tableau du chapitre 5 (map, filter…), setTimeout (« appelle cette fonction dans 2 secondes ») et addEventListener (« appelle cette fonction au clic ») :
setTimeout(() => console.log('Sauvegarde terminée'), 2000);
Une fonction peut aussi renvoyer une fonction, comme creerCompteur. On parle de fonctions d'ordre supérieur :
function creerFiltre(categorie) {
return (p) => p.categorie === categorie;
}
const estMemoire = creerFiltre('Mémoire');
estMemoire({ categorie: 'Mémoire' }); // true
estMemoire({ categorie: 'Écran' }); // false
Fonctions pures et effets de bord
Une fonction est pure quand son résultat ne dépend que de ses paramètres et qu'elle ne modifie rien en dehors d'elle : ni variable globale, ni objet reçu, ni page, ni stockage. Appelée deux fois avec les mêmes arguments, elle renvoie deux fois la même chose.
// Impure : elle modifie l'objet reçu
function retirerUniteImpure(piece) {
piece.quantite -= 1;
return piece;
}
// Pure : elle renvoie une nouvelle pièce et laisse l'originale intacte
const retirerUnite = (piece) => ({ ...piece, quantite: piece.quantite - 1 });
const ram = { ref: 'RAM-16', quantite: 3 };
const apres = retirerUnite(ram);
console.log(ram.quantite, apres.quantite); // 3 2
La syntaxe { ...piece, quantite: … } copie toutes les propriétés de piece dans un nouvel objet, puis remplace quantite (chapitre 6).
Une application a forcément des effets de bord : afficher, enregistrer, appeler une API. L'idée est de les isoler dans quelques fonctions et de garder pures toutes les règles métier : elles se comprennent seules, se réutilisent, et se testent en une ligne (chapitre 14). C'est ainsi que Gestock sépare stock.js (règles pures) de vue.js (affichage) au chapitre 11.
Documenter une fonction avec JSDoc
Un commentaire JSDoc juste au-dessus d'une fonction décrit ses paramètres et son résultat. VS Code l'affiche au survol et s'en sert pour signaler les erreurs de type (chapitre 14) :
/**
* Indique si une pièce doit être recommandée.
* @param {{ quantite: number, seuil: number }} piece
* @returns {boolean} true si la quantité est inférieure ou égale au seuil
*/
function estEnAlerte(piece) {
return piece.quantite <= piece.seuil;
}
Fil rouge : les premières règles de Gestock
Les deux règles qui reviendront partout dans l'application sont des fonctions pures :
function estEnAlerte(piece) {
return piece.quantite <= piece.seuil;
}
function valeurStock(pieces) {
let total = 0;
for (const p of pieces) {
total += p.quantite * p.prix;
}
return total;
}
console.log(valeurStock(pieces)); // 970.8
console.log(estEnAlerte(pieces[1])); // true (RAM-16 : 3 ≤ 4)
Au chapitre suivant, valeurStock tiendra en une ligne grâce à reduce.
Exercices
Exercice 1 sur 4 Réécrire en fonction fléchée Réussi À revoir
Réécris chaque fonction en fonction fléchée la plus courte possible, rangée dans une const.
function double(n) { return n * 2; }
function bonjour() { return 'Bonjour'; }
function resume(p) { return { ref: p.ref, enAlerte: p.quantite <= p.seuil }; }
Voir la réponseMasquer la réponse de l'exercice 1
const double = (n) => n * 2;
const bonjour = () => 'Bonjour';
const resume = (p) => ({ ref: p.ref, enAlerte: p.quantite <= p.seuil });
Pour resume, les parenthèses autour de l'objet sont indispensables : sans elles, la fonction renverrait undefined.
Tu avais trouvé ?
Exercice 2 sur 4 Que vaut le résultat ? Réussi À revoir
Donne la valeur de chaque appel.
function prixTTC(prixHT, taux = 0.2) {
return prixHT * (1 + taux);
}
function aire(l, h) {
l * h;
}
prixTTC(50)prixTTC(50, 0)prixTTC(50, undefined)aire(3, 4)
Voir la réponseMasquer la réponse de l'exercice 2
60: le taux par défaut 0,2 s'applique.50:0est une vraie valeur, la valeur par défaut ne s'applique pas.60:undefineddéclenche la valeur par défaut.undefined: la fonction calculel * hmais oublie lereturn.
Tu avais trouvé ?
Exercice 3 sur 4 Un générateur de numéros de bon Réussi À revoir
Écris une fonction creerNumeroteur(prefixe) qui renvoie une fonction. Chaque appel de cette fonction renvoie le numéro suivant sous la forme BON-001, BON-002… avec le préfixe donné et trois chiffres. Indice : String(n).padStart(3, '0').
Voir la réponseMasquer la réponse de l'exercice 3
function creerNumeroteur(prefixe) {
let n = 0;
return () => {
n++;
return `${prefixe}-${String(n).padStart(3, '0')}`;
};
}
const prochainBon = creerNumeroteur('BON');
prochainBon(); // 'BON-001'
prochainBon(); // 'BON-002'
La variable n vit dans la closure : chaque numéroteur a son propre compteur, que le reste du code ne peut pas modifier.
Tu avais trouvé ?
Exercice 4 sur 4 Pure ou impure ? Réussi À revoir
Pour chaque fonction, dis si elle est pure, et sinon quel est son effet de bord.
const ttc = (ht) => ht * 1.2;
let compteur = 0;
const compter = () => ++compteur;
const afficher = (p) => { document.querySelector('#titre').textContent = p.nom; };
const ajouter = (liste, p) => { liste.push(p); return liste; };
const ajouterPur = (liste, p) => [...liste, p];
Voir la réponseMasquer la réponse de l'exercice 4
ttc: pure.compter: impure, elle modifie la variable externecompteur; deux appels ne renvoient pas la même chose.afficher: impure, elle modifie la page.ajouter: impure, elle modifie le tableau reçu avecpush.ajouterPur: pure, elle crée un nouveau tableau qui contient les éléments delistepuisp.
Tu avais trouvé ?