2.1 · Fonctions fléchées
Les diapositives de la séance 2
- écrire une fonction fléchée sous sa forme longue et sous sa forme courte
- distinguer un corps d’instructions d’un corps d’expression
- reconnaître le retour implicite et le piège des accolades
- typer une fonction reçue en paramètre
- utiliser des fonctions de rappel avec
setTimeout,setIntervaletaddEventListener
Les types de base et les template literals : 1.1 · Hello world TypeScript. Les écouteurs d’événements : 1.2 · Une page web avec Astro.
Le code de ce chapitre : télécharger app.ts. Il ne touche pas au document, vous pouvez donc l’exécuter directement dans un terminal :
npx tsx app.tsUne fonction rangée dans une variable
En C, une fonction est une entité à part : elle a un nom, elle vit au niveau du fichier, on ne peut pas la créer au milieu d’un calcul. En TypeScript, une fonction est une valeur comme une autre : elle se range dans une variable, se passe en paramètre, se renvoie en résultat.
// Déclaration classique
function ajouter(a: number, b: number): number { return a + b; }
// Même chose, en fonction fléchée rangée dans une constante
const ajouter2 = (a: number, b: number): number => a + b;On dit fonction fléchée (à cause de =>), fonction anonyme (elle n’a pas de nom propre, seule la constante en a un) ou lambda. Ce sont trois mots pour la même chose.
La syntaxe générale :
(paramètres) => corps
Instructions ou expression : la vraie différence
Le corps d’une fonction fléchée s’écrit de deux façons, et c’est la distinction centrale du chapitre.
Corps d’instructions : avec accolades
app.ts
const ajouter = (a: number, b: number): void => {
const somme = a + b;
console.log(`La somme est: ${somme}`);
};
ajouter(5, 3); // affiche "La somme est: 8"Les accolades délimitent un bloc : plusieurs lignes, des variables locales, des if, des boucles. Rien n’est renvoyé automatiquement : il faut écrire return. Ici la fonction ne renvoie rien, d’où le type de retour void.
Corps d’expression : sans accolades
app.ts
const multiplier = (a: number, b: number): number => a * b;
console.log(multiplier(5, 3)); // affiche 15Une seule expression, dont la valeur est renvoyée implicitement. Pas d’accolades, pas de return, pas de point-virgule interne.
| Corps d’instructions | Corps d’expression | |
|---|---|---|
| Écriture | => { ... } |
=> expression |
| Nombre de lignes | autant qu’on veut | une seule |
| Retour | explicite, avec return |
implicite |
Sans return |
renvoie undefined |
impossible d’oublier |
| Bon pour | une logique, des cas, des effets | une transformation courte |
const doubler = (x: number) => { x * 2 }; // renvoie undefined !Les accolades ouvrent un bloc d’instructions : x * 2 y est calculé puis jeté. Le compilateur ne s’en plaint pas si vous n’avez pas annoté le type de retour : il en déduit void. Deux corrections, au choix :
const doubler = (x: number): number => x * 2; // on enlève les accolades
const doubler2 = (x: number): number => { return x * 2; }; // on ajoute returnAnnoter le type de retour transforme cette erreur silencieuse en erreur de compilation. C’est la meilleure raison de le faire.
() => { nom: "Alice" } ne renvoie pas un objet : les accolades sont lues comme un bloc. Entourez l’objet de parenthèses : () => ({ nom: "Alice" }). Le sujet revient au chapitre 2.2 · Objets de données.
La liste de paramètres
const rien = () => console.log("aucun paramètre"); // parenthèses vides obligatoires
const carre = (x: number): number => x * x;En JavaScript, les parenthèses sont facultatives pour un paramètre unique. En TypeScript, gardez-les toujours : elles sont obligatoires dès qu’il y a une annotation de type, et le typage est précisément ce qui vous protège.
Une fonction comme paramètre
C’est l’usage qui justifie tout le reste. Le type d’un paramètre qui est une fonction s’écrit avec la même flèche :
app.ts
const afficher = (
a: number,
b: number,
operation: (a: number, b: number) => number // ← le type : deux number, renvoie un number
) => {
console.log(`Le résultat est: ${operation(a, b)}`);
};
afficher(5, 3, multiplier); // 15
afficher(5, 3, (a: number, b: number) => a / b); // 1.6666666666666667
afficher(5, 3, Math.pow); // 125Lisez (a: number, b: number) => number comme une signature : « une fonction qui prend deux nombres et rend un nombre ». N’importe quelle fonction de cette forme convient, quel que soit son nom.
afficher(5, 3, ajouter); // ERREUR : ajouter renvoie void, pas numberLe message parle de void non assignable à number. Le compilateur vérifie la signature entière, pas seulement le nombre de paramètres.
Rien n’empêche de passer une fonction à corps d’instructions, du moment qu’elle renvoie bien un number :
app.ts
afficher(5, 3, (a: number, b: number) => {
let resultat: string = "";
for (let i = 0; i < a; i++) {
resultat += b;
}
return parseInt(resultat);
});
// Le résultat est: 33333int (*op)(int, int) est l’ancêtre direct de (a: number, b: number) => number. Deux différences : la notation TypeScript se lit de gauche à droite sans acrobatie, et surtout vous pouvez écrire la fonction sur place, à l’endroit de l’appel, au lieu de la déclarer ailleurs dans le fichier.
Fonctions de rappel et temps
setTimeout et setInterval reçoivent une fonction et décident quand l’appeler. Vous ne l’appelez jamais vous-même : c’est le principe d’une fonction de rappel (callback).
app.ts
// Une seule fois, après 4 secondes
setTimeout(() => {
console.log("4 secondes se sont écoulées");
}, 4000);
// Toutes les 9 secondes, indéfiniment
setInterval(() => {
console.log("répétition: 9 secondes se sont écoulées");
}, 9000);Les durées sont en millisecondes. setInterval ne s’arrête jamais tout seul : conservez la valeur qu’il renvoie pour pouvoir l’interrompre avec clearInterval(identifiant).
setTimeout(afficherVariables(), 1000) appelle la fonction immédiatement et passe son résultat à setTimeout. Écrivez setTimeout(afficherVariables, 1000), sans parenthèses : on passe la fonction, on ne l’exécute pas. Même règle pour addEventListener.
Accéder aux variables extérieures
Une fonction fléchée voit les variables déclarées autour d’elle, au moment où elle s’exécute :
app.ts
let x = 5;
const afficherVariables = () => {
console.log(`x: ${x}`);
};
x = 42;
afficherVariables(); // affiche "x: 42", pas "x: 5"Cette capture de l’environnement s’appelle une fermeture (closure). Elle permet de fabriquer des fonctions sur mesure :
const saluer = (nom: string) => {
return () => console.log(`Bonjour, ${nom}!`);
};
const saluerAlice = saluer("Alice"); // rien ne s'affiche encore
saluerAlice(); // affiche "Bonjour, Alice!"saluer ne salue personne : elle fabrique une fonction qui, elle, saluera. La valeur de nom reste attachée à la fonction produite.
Exercices
Exercice 2.1 : Coordonnées du clic
essentiel
Dans un projet Astro, faites charger app.ts par une page pour disposer de document, et ajoutez :
document.addEventListener("click", (event: MouseEvent) => {
console.log(`x: ${event.clientX}, y: ${event.clientY}`);
});Le paramètre event n’est pas fourni par vous : le navigateur le passe à chaque clic.
- Vérifiez dans la console que les coordonnées changent d’un clic à l’autre.
- Remplacez
clientXparpageXet cliquez après avoir fait défiler la page. Que change ce choix ?
Réussite : vous savez expliquer qui appelle la fonction fléchée, et d’où vient son paramètre.
src/pages/index.astro
---
---
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Coordonnées du clic</title>
</head>
<body style="height: 3000px">
<h1>Cliquez n'importe où, puis faites défiler et recliquez</h1>
<script src="../scripts/app.ts"></script>
</body>
</html>La page est volontairement très haute : sans défilement possible, la question 2 ne montre rien.
src/scripts/app.ts
document.addEventListener("click", (event: MouseEvent) => {
console.log(`client x: ${event.clientX}, y: ${event.clientY}`);
console.log(`page x: ${event.pageX}, y: ${event.pageY}`);
});2. Tant que la page n’a pas défilé, les deux couples de valeurs sont identiques. Défilez de 500 pixels, cliquez au même endroit de l’écran : clientY n’a pas bougé, pageY a augmenté de 500.
clientX/clientY: position par rapport à la fenêtre visible. L’origine est le coin haut-gauche de ce que vous voyez, et elle se déplace avec le défilement.pageX/pageY: position par rapport au document entier. L’origine est le haut du document, et elle ne bouge jamais.
La relation est simple : pageY === clientY + window.scrollY.
Le choix découle de l’usage. Pour positionner une infobulle ou un menu contextuel qui suit le curseur à l’écran, clientX/clientY ; pour mémoriser « l’utilisateur a cliqué à cet endroit du document » et le retrouver plus tard, pageX/pageY.
Le point clé : la question « qui appelle cette fonction ? » est le cœur du chapitre. Vous n’appelez jamais cette fonction fléchée ; vous la confiez au navigateur, qui la garde de côté et l’appellera lui-même à chaque clic, en lui passant un objet MouseEvent décrivant ce qui vient de se produire. C’est un renversement du flux de contrôle : votre code ne décide plus du quand, il décrit seulement le quoi.
C’est aussi ce qui explique que la fonction soit anonyme : elle n’a pas de nom parce que vous n’aurez jamais à l’appeler par son nom.
Le MouseEvent contient bien plus que des coordonnées : event.target (l’élément cliqué), event.button (quel bouton de la souris), event.shiftKey (la touche Maj était-elle enfoncée). L’annotation : MouseEvent vous donne accès à tout cela dans l’autocomplétion : avec un simple : Event, clientX serait refusé, car tous les événements n’ont pas de coordonnées.
Exercice 2.2 : Calculatrice
essentiel
- Écrivez quatre fonctions fléchées
additionner,soustraire,multiplier,diviser, chacune en corps d’expression (une ligne, sans accolades nireturn). - Écrivez
calculatrice(a: number, b: number, operation: string)en corps d’instructions, qui choisit la bonne fonction et affiche le résultat.calculatrice(5, 3, "additionner")doit afficherLa somme est: 8. - Gérez l’opération inconnue et la division par zéro.
Réussite : les quatre opérations tiennent chacune sur une ligne, et calculatrice(5, 0, "diviser") n’affiche pas Infinity.
Un switch sur operation, ou mieux : un type littéral "additionner" | "soustraire" | "multiplier" | "diviser" à la place de string, et l’éditeur vous proposera les quatre valeurs.
calculatrice.ts
// 1. Les quatre opérations, en corps d'expression : une ligne, sans accolades
const additionner = (a: number, b: number): number => a + b;
const soustraire = (a: number, b: number): number => a - b;
const multiplier = (a: number, b: number): number => a * b;
const diviser = (a: number, b: number): number => a / b;
// Le type littéral remplace string : seules ces quatre chaînes sont acceptées
type Operation = "additionner" | "soustraire" | "multiplier" | "diviser";
// 2. et 3. La calculatrice, en corps d'instructions
const calculatrice = (a: number, b: number, operation: Operation): void => {
// 3. Division par zéro : on traite le cas AVANT de calculer
if (operation === "diviser" && b === 0) {
console.log("Division par zéro impossible");
return;
}
switch (operation) {
case "additionner":
console.log(`La somme est: ${additionner(a, b)}`);
break;
case "soustraire":
console.log(`La différence est: ${soustraire(a, b)}`);
break;
case "multiplier":
console.log(`Le produit est: ${multiplier(a, b)}`);
break;
case "diviser":
console.log(`Le quotient est: ${diviser(a, b)}`);
break;
default:
console.log(`Opération inconnue: ${operation}`);
}
};
calculatrice(5, 3, "additionner"); // La somme est: 8
calculatrice(10, 4, "soustraire"); // La différence est: 6
calculatrice(6, 7, "multiplier"); // Le produit est: 42
calculatrice(15, 4, "diviser"); // Le quotient est: 3.75
calculatrice(5, 0, "diviser"); // Division par zéro impossibleLe point clé : la division par zéro est le véritable sujet de l’exercice. 5 / 0 ne provoque aucune erreur en JavaScript : le résultat est Infinity, une valeur parfaitement légitime de type number. Le compilateur ne dira rien, l’exécution non plus, et la valeur va se propager tranquillement dans la suite de vos calculs : Infinity - Infinity donnant NaN, qui contamine ensuite tout ce qu’il touche. C’est un piège bien pire qu’un plantage : rien ne signale l’anomalie. Le test explicite sur b === 0 est donc à votre charge, personne ne le fera à votre place.
Sur le type littéral : avec operation: string, calculatrice(5, 3, "addition") compile sans broncher et affiche Opération inconnue à l’exécution. Avec le type Operation, la faute de frappe devient une erreur de compilation, et l’éditeur propose les quatre valeurs valides dès la première lettre tapée. Le default du switch devient d’ailleurs inatteignable. Gardez-le tout de même : il reste le filet de sécurité si la valeur vient d’un formulaire ou d’une API, où les types ne protègent plus rien.
Alternative : un objet qui associe chaque nom à sa fonction supprime le switch :
const operations = { additionner, soustraire, multiplier, diviser };
console.log(operations["multiplier"](6, 7)); // 42C’est plus court et se prête mieux à l’ajout d’opérations, mais on perd le message personnalisé par opération (« somme », « produit »…) que demande l’énoncé. Les objets sont le sujet du chapitre 2.2 · Objets de données.
L’erreur classique : oublier un break. Sans lui, l’exécution continue dans le case suivant et calculatrice(5, 3, "additionner") affiche la somme puis la différence.
Exercice 2.3 : Position de la souris, une fois par seconde
entraînement transfert
Affichez les coordonnées de la souris toutes les secondes, et non à chaque mouvement.
Le piège : mousemove se déclenche des dizaines de fois par seconde, setInterval n’a pas accès à l’événement. Il faut donc combiner les deux : mémoriser la dernière position connue dans des variables extérieures, et les lire depuis l’intervalle.
Réussite : une ligne par seconde dans la console, même si la souris bouge sans arrêt, et rien de nouveau si elle ne bouge plus.
src/scripts/app.ts
// Variables extérieures : elles servent de boîte aux lettres entre les deux fonctions
let dernierX: number = 0;
let dernierY: number = 0;
let aBouge: boolean = false;
// L'écrivain : appelé des dizaines de fois par seconde, il ne fait qu'enregistrer
document.addEventListener("mousemove", (event: MouseEvent) => {
dernierX = event.clientX;
dernierY = event.clientY;
aBouge = true;
});
// Le lecteur : appelé une fois par seconde, il affiche et remet le drapeau à zéro
setInterval(() => {
if (aBouge) {
console.log(`Position de la souris : x: ${dernierX}, y: ${dernierY}`);
aBouge = false;
}
}, 1000);Le point clé : deux fonctions de rappel, appelées par deux déclencheurs différents et à des rythmes sans rapport, qui communiquent uniquement par des variables déclarées à l’extérieur des deux. C’est la seule solution possible, et pour une raison de fond : setInterval n’a aucun moyen d’accéder à un MouseEvent, puisque son appel n’est déclenché par aucun mouvement de souris. L’information doit donc être mémorisée au moment où elle est disponible, pour être relue plus tard.
Les deux fonctions voient dernierX et dernierY par fermeture : une fonction fléchée capture les variables de son environnement de déclaration et lit leur valeur au moment où elle s’exécute, pas au moment où elle est écrite. C’est exactement l’exemple x = 42 vu plus haut dans le chapitre.
C’est aussi le seul endroit du corrigé où let s’impose : ces trois valeurs changent en permanence, const les rendrait inutilisables.
Sur le drapeau aBouge : sans lui, l’intervalle réafficherait la même position chaque seconde, souris immobile comprise, et noierait la console. C’est ce que demande la dernière condition de réussite. Le motif porte un nom, throttling (limitation de fréquence) : on reçoit tous les événements, on n’en traite qu’un par période. Il est indispensable dès que la réaction à un événement coûte cher (requête réseau, redécoupage de la page), car mousemove, scroll et resize se déclenchent facilement soixante fois par seconde.
Pour bien faire : setInterval renvoie un identifiant qu’il faut conserver si l’on veut pouvoir arrêter la répétition :
const timer = setInterval(() => { /* … */ }, 1000);
// plus tard : clearInterval(timer);QCM
1. Dans une fonction fléchée, qu’est-ce qui distingue un corps d’expression d’un corps d’instructions ?
A. Ils sont interchangeables : seule la présence du mot-clé return les distingue. B. L’expression s’écrit sans accolades et sa valeur est renvoyée ; les instructions exigent des accolades et ne renvoient rien automatiquement. C. Les expressions sont interdites dans les fonctions fléchées. D. Il n’y a aucune différence.
B. Les accolades ouvrent un bloc : sans return, la fonction renvoie undefined.
2. Une lambda à corps d’instructions, par rapport à une lambda à corps d’expression :
A. ne peut pas renvoyer de valeur. B. utilise des accolades et peut contenir plusieurs instructions, alors que la forme à expression renvoie directement le résultat d’une expression unique. C. ne peut pas avoir de paramètres. D. ne peut pas être rangée dans une const.
B. La forme à instructions peut tout à fait renvoyer une valeur, à condition d’écrire return.
3. Qu’affiche ce code ?
setTimeout(() => {
console.log("4");
}, 4000);
setInterval(() => {
console.log("9");
}, 9000);A. « 4 » une fois après 4 secondes, « 9 » une fois après 4 secondes. B. « 4 » une fois après 4 secondes, « 9 » toutes les 9 secondes. C. « 4 » toutes les 4 secondes, « 9 » une fois après 9 secondes. D. « 4 » toutes les 4 secondes, « 9 » toutes les 9 secondes.
B. setTimeout déclenche une seule fois ; setInterval répète jusqu’à clearInterval.
4. Que fait saluer ?
const saluer = (nom: string) => {
return () => console.log(`Bonjour, ${nom}!`);
};A. Elle affiche un message de salutation. B. Elle renvoie le message de salutation sous forme de chaîne. C. Elle renvoie une fonction qui prend un nom en paramètre. D. Elle renvoie une fonction qui affiche un message de salutation.
D. saluer("Alice") n’affiche rien : elle produit une fonction. C’est l’appel de cette fonction qui déclenche l’affichage.
À retenir
(params) => expressionrenvoie la valeur ;(params) => { ... }exige unreturn- Annoter le type de retour transforme l’oubli de
returnen erreur de compilation - Une fonction est une valeur : elle se range, se passe et se renvoie
- Le type d’une fonction s’écrit
(a: number, b: number) => number - On passe une fonction de rappel (
maFonction), on ne l’appelle pas (maFonction()) - Une fonction fléchée voit les variables déclarées autour d’elle : c’est une fermeture