Les closures en JavaScript : principes fondamentaux et cas d'utilisation

La portée des variables en JavaScript

En JavaScript, les variables ont deux portées principales : globale et locale. Une particularité du langage est qu'une fonction peut accéder directement aux variables globales.


const valeurGlobale = 50;

function afficherGlobale() {
  console.log(valeurGlobale);
}

afficherGlobale(); // 50

En revanche, une variable déclarée avec let ou const à l'intérieur d'une fonction n'est pas accessible à l'extérieur.


function creerLocale() {
  let valeurLocale = 100;
}

console.log(valeurLocale); // Erreur : valeurLocale n'est pas définie

Si on omet let ou const, la variable devient implicitement globale, ce qui est une pratique déconseilée.


function definirVariable() {
  sansDeclaration = 300;
}

definirVariable();
console.log(sansDeclaration); // 300

Accès aux variables locales depuis l'extérieur

Pour lire une variable locale depuis l'extérieur d'une fonction, on peut imbriquer une fonction interne. Cette fonction interne peut accéder aux variables de sa fonction parente grâce à la chaîne de portée.


function fonctionParente() {
  let compteur = 0;
  function fonctionEnfant() {
    compteur++;
    console.log(compteur);
  }
  return fonctionEnfant;
}

const reference = fonctionParente();
reference(); // 1
reference(); // 2

Ici, fonctionEnfant est une closure : elle capture la varible compteur de sa portée externe et la maintient en mémoire même après l'exécution de fonctionParente.

Définition des closures

Une closure est une fonction qui se souvient de son environnement lexical, c'est-à-dire des variables présentes lors de sa définition. Cela permet à la fonction d'accéder à ces variables même si elle est exécutée hors de cette portée.

En pratique, toute fonction dans JavaScript peut être considérée comme une closure, mais les fonctions imbriquées offrent les cas d'utilisation les plus courants.

Utilisations principales des closures

Les closures servent à deux choses essentielles : accéder aux variables locales d'une fonction depuis l'extérieur, et préserver ces variables en mémoire entre les appels.


function creerCompteur() {
  let total = 0;
  const incrementer = function() {
    total += 1;
  };
  const lire = function() {
    console.log(total);
  };
  return { incrementer, lire };
}

const compteur = creerCompteur();
compteur.incrementer();
compteur.lire(); // 1
compteur.incrementer();
compteur.lire(); // 2

Dans cet exemple, total reste en mémoire grâce à la closure. Notez que incrementer n'a pas de déclaration var, donc il est accessible globalement via l'objet retourné, mais il agit sur la variable privée total.

Précautions d'utilisation

Puisque les closures maintiennent des variables en mémoire, une utilisation excessive peut entraîner des fuites de mémoire, notamment dans les navigateurs plus anciens. Il est recommandé de libérer les variables inutilisées en les définissant à null lorsque possible.

De plus, les closures peuvent modifier les variables de la fonction parente, ce qui peut être source d'effets de bord imprévus si on les utilise comme méthodes publiques d'un objet.

Exercices et exemples approfondis

Considérons le code suivant pour comprendre le comportement des closures :


const nomGlobal = "Fenêtre";

const objet = {
  nom: "Mon Objet",
  obtenirNom: function() {
    return function() {
      return this.nom;
    };
  }
};

console.log(objet.obtenirNom()()); // "Fenêtre"

Ici, la fonction retournée perd le contexte de objet et utilise this qui se réfère à l'objet global, d'où l'affichage de nomGlobal.

Portée des variables et chaîne de portée

La portée d'une fonction est déterminée à sa définition, pas à son exécution. Voici un exemple qui illustre ce point :


function creerFonction(valeur) {
  const fonctionInterne = function() {
    return valeur;
  };
  return fonctionInterne;
}

const maFonction = creerFonction(42);
console.log(maFonction()); // 42

La fonction fonctionInterne capture valeur au moment de sa création dans creerFonction, ce qui explique pourquoi elle retourne toujours la même valeur.

Applications pratiques des closures

Les closures permettent de créer des propriétés et méthodes privées dans les objets JavaScript :


function Personne(nom, age) {
  let _nom = nom;
  let _age = age;
  
  this.obtenirDetails = function() {
    return `${_nom}, ${_age} ans`;
  };
}

const personne = new Personne("Alice", 30);
console.log(personne.obtenirDetails()); // "Alice, 30 ans"
console.log(personne._nom); // undefined

Ici, _nom et _age sont protégés par la closure et ne sont accessibles que via obtenirDetails.

Étiquettes: JavaScript closures scope functions lexical-environment

Publié le 11 juin à 01h31