Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Angular

Les fonctions flechees en javascript

Cette leçon présente les fonctions fléchées, apparues avec ES6 : leurs cinq formes d'écriture, la différence entre corps concis (retour implicite) et corps en bloc, et les deux erreurs les plus fréque...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Cette leçon présente les fonctions fléchées, apparues avec ES6 : leurs cinq formes d'écriture, la différence entre corps concis (retour implicite) et corps en bloc, et les deux erreurs les plus fréquentes, l'oubli de return et l'objet renvoyé sans parenthèses. Vous comprendrez pourquoi une fonction fléchée garde le this du code qui l'entoure, ce qui en fait le meilleur choix pour les callbacks de map, filter, forEach ou setTimeout écrits dans une classe. Vous verrez aussi leurs limites : pas d'arguments, pas de new, et pourquoi on ne les utilise pas pour les méthodes d'un objet. Des exemples tirés d'un composant Angular, des schémas et cinq activités pratiques accompagnent chaque notion.
  1. Objectifs

    • Écrire une fonction fléchée sous ses différentes formes
    • Distinguer le corps concis (retour implicite) et le corps en bloc
    • Comprendre le comportement de this dans une fonction fléchée
    • Connaître les limites des fonctions fléchées : pas de arguments, pas de new, pas de méthode d'objet utilisant this
    • Utiliser les fonctions fléchées comme callbacks et écrire des fonctions qui renvoient des fonctions
    • Reconnaître et utiliser les fonctions fléchées dans un composant Angular
  2. Présentation

    • Une fonction fléchée (arrow function) est une façon courte d'écrire une fonction, apparue avec ES6 (ES2015). Elle doit son nom à la flèche => qui sépare les paramètres du corps de la fonction.
    • Elle apporte deux avantages :
      • une syntaxe concise, très lisible pour les petites fonctions passées en paramètre (map, filter, setTimeout…) ;
      • un this lexical: la fonction fléchée n'a pas son propre this, elle utilise celui du code qui l'entoure. Elle supprime ainsi l'une des plus grandes sources de bugs de JavaScript.
    • Dans un projet Angular, les fonctions fléchées sont partout : traitement des listes, minuteries, réponses du client HTTP, signals calculés…

Anatomie d'une fonction fléchée

  1. Trois façons d'écrire une fonction

    • La même fonction peut s'écrire de trois manières en JavaScript :
// 1. Déclaration de fonction
function prixTTC1(ht) {
  return ht * 1.19;
}

// 2. Expression de fonction (fonction anonyme rangée dans une constante)
const prixTTC2 = function (ht) {
  return ht * 1.19;
};

// 3. Fonction fléchée
const prixTTC3 = (ht) => ht * 1.19;

console.log(prixTTC1(100), prixTTC2(100), prixTTC3(100));   // 119 119 119
  • Une fonction fléchée est toujours anonyme: pour la réutiliser, on la range dans une constante (const), ou on la passe directement en paramètre à une autre fonction.
  • Attention : contrairement à une déclaration de fonction, une fonction fléchée rangée dans une const n'est pas remontée (hoisting) : on ne peut pas l'appeler avant sa ligne de définition (voir let, const et portée des variables).
test();   // ReferenceError: Cannot access 'test' before initialization
const test = () => 1;
  1. Les différentes formes

    • La syntaxe s'adapte au nombre de paramètres et à la longueur du corps :

Les cinq formes d'une fonction fléchée

const direBonjour = () => 'Bonjour';           // aucun paramètre : () obligatoires
const doubler = n => n * 2;                     // un seul paramètre : () facultatives
const additionner = (a, b) => a + b;            // plusieurs paramètres : () obligatoires
const saluer = (nom = 'stagiaire') => `Bonjour ${nom}`;   // paramètre par défaut

const mention = (moyenne) => {                  // plusieurs instructions : bloc + return
  if (moyenne >= 16) return 'Très bien';
  if (moyenne >= 10) return 'Admis';
  return 'Ajourné';
};

const creerStagiaire = (nom, groupe) => ({ nom, groupe, actif: true });   // objet : ( )

console.log(direBonjour(), doubler(21), additionner(2, 3), saluer(), mention(17));
// Bonjour 42 5 Bonjour stagiaire Très bien
console.log(creerStagiaire('Amira', 'DEV1'));
// { nom: 'Amira', groupe: 'DEV1', actif: true }
  • Conseil : le formateur Prettier, installé avec VS Code, ajoute automatiquement les parenthèses autour d'un paramètre unique : (n) => n * 2. Cette écriture est plus uniforme ; c'est celle utilisée dans la suite du cours.
  • Corps concis et corps en bloc

    • Corps concis: une seule expression, sans accolades. Sa valeur est renvoyée automatiquement (retour implicite).
    • Corps en bloc: des accolades { } contenant des instructions. Rien n'est renvoyé sans le mot-clé return.
    • Deux erreurs sont très fréquentes :
    // Erreur 1 : accolades sans return → la fonction renvoie undefined
    const tripler = (n) => { n * 3; };
    console.log(tripler(5));         // undefined
    
    // Erreur 2 : objet renvoyé sans parenthèses
    const creer = (nom) => { nom: nom };
    console.log(creer('Sami'));      // undefined : { } est lu comme un bloc, "nom:" comme une étiquette
    
    const creer2 = (nom, groupe) => { nom: nom, groupe: groupe };
    // SyntaxError: Unexpected token ':'
    
    // Corrections
    const triplerOk = (n) => n * 3;                  // ou { return n * 3; }
    const creerOk = (nom, groupe) => ({ nom, groupe });
    1. this dans une fonction fléchée

      • Dans une fonction classique, this dépend de la façon dont la fonction est appelée. Quand on passe une fonction classique à forEach, à setTimeout ou à un autre code, elle perd le this de l'objet.
      • Une fonction fléchée n'a pas de this: elle utilise celui de l'endroit où elle est écrite. On dit que son this est lexical.

    this dans une fonction classique et dans une fonction fléchée

    class Formateur {
      constructor(nom, groupes) {
        this.nom = nom;
        this.groupes = groupes;
      }
    
      afficherClassique() {
        this.groupes.forEach(function (g) {
          console.log(`${this.nom} enseigne en ${g}`);   // this vaut undefined ici
        });
      }
    
      afficherFlechee() {
        this.groupes.forEach((g) => console.log(`${this.nom} enseigne en ${g}`));
      }
    }
    
    const ali = new Formateur('Ali', ['DEV1', 'DEV2']);
    ali.afficherFlechee();
    // Ali enseigne en DEV1
    // Ali enseigne en DEV2
    ali.afficherClassique();
    // TypeError: Cannot read properties of undefined (reading 'nom')
    • Avant ES6, on contournait le problème en stockant this dans une variable (var self = this;) ou avec .bind(this). Les fonctions fléchées rendent ces astuces inutiles.
    • Exemple : une minuterie dans une classe
    class Chrono {
      secondes = 0;
    
      demarrer() {
        const id = setInterval(() => {
          this.secondes++;                          // this = l'objet Chrono
          console.log(`Chrono : ${this.secondes} s`);
          if (this.secondes === 3) clearInterval(id);
        }, 1000);
      }
    }
    
    new Chrono().demarrer();
    // Chrono : 1 s
    // Chrono : 2 s
    // Chrono : 3 s
    1. Une méthode passée en paramètre perd son this

      • Le problème existe aussi quand on passe une méthode à une autre fonction : elle est alors appelée seule, sans son objet.
      • Une solution consiste à déclarer la méthode comme une propriété contenant une fonction fléchée: son this reste attaché à l'objet.
    class Compteur {
      valeur = 0;
    
      incrementer() {                         // méthode classique
        this.valeur++;
        return this.valeur;
      }
    
      incrementerFlechee = () => {            // propriété contenant une fonction fléchée
        this.valeur++;
        return this.valeur;
      };
    }
    
    const c = new Compteur();
    
    const ref1 = c.incrementer;
    ref1();          // TypeError: Cannot read properties of undefined (reading 'valeur')
    
    const ref2 = c.incrementerFlechee;
    console.log(ref2(), ref2());              // 1 2
    
    setTimeout(c.incrementerFlechee, 0);      // fonctionne aussi
    1. Ce que les fonctions fléchées n'ont pas

      • Pas de this propre: c'est leur force dans les callbacks, mais un défaut pour les méthodes d'un objet.
    const groupe = {
      nom: 'DEV1',
      presenterFlechee: () => `Groupe ${this.nom}`,   // this n'est pas l'objet groupe
      presenter() {                                    // syntaxe de méthode : correct
        return `Groupe ${this.nom}`;
      },
    };
    console.log(groupe.presenterFlechee());   // Groupe undefined
    console.log(groupe.presenter());          // Groupe DEV1
    • Pas d'objet arguments: on utilise le paramètre rest ...valeurs, qui donne un vrai tableau.
    function compterClassique() {
      return arguments.length;
    }
    const compterFlechee = (...valeurs) => valeurs.length;
    
    console.log(compterClassique(1, 2, 3), compterFlechee(1, 2, 3));   // 3 3
    • Pas de constructeur: une fonction fléchée ne peut pas être appelée avec new, et elle n'a pas de propriété prototype. Pour créer des objets, on utilise une classe.
    const Personne = (nom) => {
      this.nom = nom;
    };
    new Personne('Ali');   // TypeError: Personne is not a constructor
    • Dans le navigateur, un écouteur d'événement écrit avec une fonction classique reçoit l'élément cliqué dans this; une fonction fléchée, non. On utilise alors le paramètre de l'événement :
    const bouton = document.querySelector('#valider');
    
    bouton.addEventListener('click', function () {
      this.classList.add('active');           // this = le bouton
    });
    
    bouton.addEventListener('click', (event) => {
      event.currentTarget.classList.add('active');   // this n'est pas le bouton : on passe par l'événement
    });

    Quand utiliser une fonction fléchée

    1. Les fonctions fléchées comme callbacks

      • Un callback (fonction de rappel) est une fonction passée en paramètre à une autre fonction, qui l'appellera au bon moment. C'est l'usage principal des fonctions fléchées.
    const stagiaires = [
      { nom: 'Amira', groupe: 'DEV1', moyenne: 15.5 },
      { nom: 'Sami', groupe: 'DEV2', moyenne: 9.75 },
      { nom: 'Yasmine', groupe: 'DEV1', moyenne: 12 },
      { nom: 'Karim', groupe: 'DEV2', moyenne: 17.25 },
    ];
    
    // Avant ES6
    var nomsAdmis = stagiaires
      .filter(function (s) { return s.moyenne >= 10; })
      .map(function (s) { return s.nom; });
    
    // Avec des fonctions fléchées
    const admis = stagiaires
      .filter((s) => s.moyenne >= 10)
      .map((s) => s.nom);
    console.log(admis);   // [ 'Amira', 'Yasmine', 'Karim' ]
    
    const moyenneGenerale = stagiaires.reduce((total, s) => total + s.moyenne, 0) / stagiaires.length;
    console.log(moyenneGenerale.toFixed(2));   // 13.63
    
    setTimeout(() => console.log('Affiché après 2 secondes'), 2000);
    1. Des fonctions qui renvoient des fonctions

      • Une fonction fléchée peut renvoyer une autre fonction fléchée. On obtient une fabrique de fonctions, paramétrée une fois puis réutilisée. Grâce à la fermeture, la fonction renvoyée se souvient du paramètre reçu.
    const multiplierPar = (facteur) => (n) => n * facteur;
    
    const triple = multiplierPar(3);
    console.log(triple(5));             // 15
    console.log(multiplierPar(10)(4));  // 40
    
    // Utilisation pratique : une remise réutilisable avec map
    const appliquerRemise = (taux) => (prix) => +(prix * (1 - taux)).toFixed(3);
    console.log([100, 45.5, 20].map(appliquerRemise(0.1)));   // [ 90, 40.95, 18 ]
    1. Les fonctions fléchées dans Angular

      • Dans un composant Angular, les méthodes s'écrivent avec la syntaxe classique (supprimer(id) { … }). Les fonctions fléchées servent pour tout ce qu'on passe en paramètre à l'intérieur de ces méthodes : elles conservent le this du composant.
    export class ListeFormations {
      formations: Formation[] = [
        { id: 1, titre: 'Développement web', placesRestantes: 6 },
        { id: 2, titre: 'Réseaux informatiques', placesRestantes: 0 },
        { id: 3, titre: 'Infographie et design', placesRestantes: 10 },
      ];
      recherche = 'web';
      message = '';
    
      get formationsFiltrees(): Formation[] {
        return this.formations.filter((f) =>
          f.titre.toLowerCase().includes(this.recherche.toLowerCase()),
        );
      }
    
      get placesTotales(): number {
        return this.formations.reduce((total, f) => total + f.placesRestantes, 0);
      }
    
      supprimer(id: number): void {
        this.formations = this.formations.filter((f) => f.id !== id);
        this.message = 'Formation supprimée';
        setTimeout(() => (this.message = ''), 3000);   // efface le message après 3 s
      }
    }
    • Vous rencontrerez aussi des fonctions fléchées dans les API d'Angular : computed(() => …) et effect(() => …) avec les signals, ou subscribe((donnees) => …) avec le client HTTP. Ces notions seront étudiées dans les chapitres suivants.
    • Dans un template : on n'écrit pas la logique dans le HTML. On appelle une méthode du composant, par exemple (click)="supprimer(f.id)", et c'est cette méthode qui utilise des fonctions fléchées.
  • Activité 1 : convertir en fonctions fléchées

    • Dans le dossier rappel-ecmascript (dépôt GitHub), créer le fichier 14-conversion.js. Réécrire chaque fonction en fonction fléchée, sous la forme la plus courte possible, puis vérifier le résultat.
    function carre(n) {
      return n * n;
    }
    function estMajeur(age) {
      return age >= 18;
    }
    function initiales(prenom, nom) {
      return prenom[0] + '.' + nom[0] + '.';
    }
    function creerProduit(nom, prix) {
      return { nom: nom, prix: prix, stock: 0 };
    }
    function moyenne(notes) {
      if (notes.length === 0) {
        return 0;
      }
      var somme = 0;
      for (var i = 0; i < notes.length; i++) {
        somme += notes[i];
      }
      return somme / notes.length;
    }
    
    console.log(carre(7), estMajeur(17), initiales('Amira', 'Ben Ali'));
    console.log(creerProduit('Clavier', 45), moyenne([12, 15, 18]), moyenne([]));
    
    // Résultat attendu :
    // 49 false A.B.
    // { nom: 'Clavier', prix: 45, stock: 0 } 15 0
    • Pour initiales, utiliser un gabarit `${ }`; pour moyenne, remplacer la boucle par reduce. Quelle fonction ne peut pas s'écrire avec un corps concis ? Pourquoi ?
  • Activité 2 : trouver les trois bugs

    • Copier ce programme dans 15-bugs.js et l'exécuter. Il contient trois erreurs liées aux fonctions fléchées.
    const prixTTC = (ht) => { ht * 1.19; };
    const creerFormation = (titre, duree) => { titre: titre, duree: duree };
    const groupe = {
      nom: 'DEV1',
      effectif: 24,
      presenter: () => `Groupe ${this.nom} : ${this.effectif} stagiaires`,
    };
    
    console.log(prixTTC(100));
    console.log(creerFormation('Web', 24));
    console.log(groupe.presenter());
    • Le programme s'arrête d'abord sur une SyntaxError: Unexpected token ':'. Quelle ligne est en cause ? La corriger.
    • Relancer : le programme affiche undefined puis Groupe undefined : undefined stagiaires. Expliquer et corriger les deux autres bugs.
    • Résultat attendu après correction :
    119
    { titre: 'Web', duree: 24 }
    Groupe DEV1 : 24 stagiaires
    1. Activité 3 : traiter une liste de commandes

      • Dans 16-commandes.js, à partir du tableau ci-dessous, écrire chaque traitement en une seule instruction, avec des méthodes de tableaux et des fonctions fléchées. Commencer par une fonction fléchée montant(c) qui calcule prix × quantité.
    const commandes = [
      { client: 'Amira', produit: 'Clavier', prix: 45, quantite: 2, payee: true },
      { client: 'Sami', produit: 'Écran', prix: 420, quantite: 1, payee: false },
      { client: 'Yasmine', produit: 'Souris', prix: 20, quantite: 3, payee: true },
      { client: 'Karim', produit: 'Casque', prix: 85, quantite: 1, payee: true },
      { client: 'Amira', produit: 'Câble HDMI', prix: 12.5, quantite: 4, payee: false },
    ];
    • les commandes payées, sous la forme client : produit (filter + map) ;
    • le chiffre d'affaires encaissé, c'est-à-dire le total des commandes payées (reduce) ;
    • les impayés, sous la forme client doit montant DT;
    • les trois produits les plus chers à la commande (toSorted + slice + map) ;
    • existe-t-il une commande de plus de 400 DT (some) ? Toutes les quantités sont-elles positives (every) ?
    [ 'Amira : Clavier', 'Yasmine : Souris', 'Karim : Casque' ]
    Chiffre d'affaires encaissé : 235.000 DT
    [ 'Sami doit 420.000 DT', 'Amira doit 50.000 DT' ]
    [ 'Écran', 'Clavier', 'Casque' ]
    true true
    1. Activité 4 : un minuteur de séance

      • Dans 17-minuteur.js, écrire une classe Minuteur avec :
        • une propriété restant (en secondes) initialisée par le constructeur ;
        • une méthode demarrer() qui, toutes les secondes, diminue restant et l'affiche, puis affiche « Fin de la séance ! » et s'arrête quand il atteint 0 (setInterval et clearInterval avec une fonction fléchée).
      • Tester avec new Minuteur(3).demarrer();. Résultat attendu :
    Il reste 2 s
    Il reste 1 s
    Il reste 0 s
    Fin de la séance !
    • Remplacer ensuite la fonction fléchée par function () { … } et relancer. Le programme affiche Il reste NaN s sans jamais s'arrêter : l'interrompre avec Ctrl + C. Expliquer en commentaire : que vaut this dans la fonction classique ? Pourquoi this.restant devient-il NaN? Pourquoi la condition d'arrêt n'est-elle jamais vraie ? Revenir ensuite à la fonction fléchée.
    • À noter : ce bug est plus sournois que celui de forEach. Appelée par setInterval, la fonction classique ne reçoit pas undefined mais un autre objet (l'objet Timeout dans Node.js, window dans le navigateur) : il n'y a donc aucune erreur, seulement un résultat faux.
  • Activité 5 : fabriques de fonctions

    • Dans 18-fabriques.js, écrire deux fonctions qui renvoient des fonctions :
      • creerFormateurPrix(devise, decimales = 3) renvoie une fonction qui formate un montant ;
      • auMoins(minimum) renvoie une fonction, utilisable dans filter, qui teste si la moyenne d'un stagiaire atteint le minimum.
    • Tester avec le code suivant :
    const enDinars = creerFormateurPrix('DT');
    const enEuros = creerFormateurPrix('€', 2);
    console.log(enDinars(45), enEuros(13.5));
    
    const stagiaires = [
      { nom: 'Amira', moyenne: 15.5 }, { nom: 'Sami', moyenne: 9.75 },
      { nom: 'Yasmine', moyenne: 12 }, { nom: 'Karim', moyenne: 17.25 },
    ];
    console.log(stagiaires.filter(auMoins(10)).map((s) => s.nom));
    console.log(stagiaires.filter(auMoins(16)).map((s) => s.nom));
    
    // Résultat attendu :
    // 45.000 DT 13.50 €
    // [ 'Amira', 'Yasmine', 'Karim' ]
    // [ 'Karim' ]
    • Faire un commit après chaque activité, puis un push : git commit -m "Fonctions fléchées : activité N".
  • Erreurs fréquentes

  • Message ou symptôme Cause Solution
    La fonction renvoie undefined Corps en bloc { } sans return Ajouter return, ou supprimer les accolades
    SyntaxError: Unexpected token ':', ou undefined au lieu d'un objet Objet renvoyé sans parenthèses Écrire () => ({ … })
    TypeError: Cannot read properties of undefined (reading 'x') dans un callback Fonction classique passée en callback : this perdu Utiliser une fonction fléchée
    Une méthode d'objet affiche undefined pour this.x Méthode écrite avec une fonction fléchée Utiliser la syntaxe de méthode nom() { … }
    TypeError: X is not a constructor new appliqué à une fonction fléchée Utiliser une classe
    ReferenceError: Cannot access 'f' before initialization Fonction fléchée appelée avant sa définition Déplacer la définition avant l'appel
    1. À retenir

      • Une fonction fléchée s'écrit (paramètres) => corps; avec un corps concis, la valeur est renvoyée automatiquement.
      • Pour renvoyer un objet en corps concis, on l'entoure de parenthèses : () => ({ … }).
      • Une fonction fléchée n'a ni this, ni arguments, et ne peut pas être appelée avec new.
      • Son this est celui du code qui l'entoure : c'est le bon choix pour tous les callbacks écrits dans une méthode de classe.
      • Méthode d'objet ou de classe → syntaxe de méthode ; fonction passée en paramètre → fonction fléchée.
    Dans un composant Angular, chaque fois que vous passez une fonction à filter, map, setTimeout ou subscribe, écrivez une fonction fléchée : this restera votre composant.