Kodokon kodokon.com

Closures et portées lexicales

Maîtrisez la capture de variables par les closures pour encapsuler de l'état et éviter les pièges de portée.

9 min · 3 questions

Ouvrir cette leçon dans Kodokon

Une closure n'est pas une astuce exotique : c'est le mécanisme par lequel une fonction conserve un accès à la portée lexicale dans laquelle elle a été définie, et non celle où elle est appelée. Le moteur ne copie pas les valeurs : il garde une référence vivante vers l'environnement lexical. Deux appels à la même fabrique produisent donc deux environnements indépendants, ce qui fait des closures l'outil d'encapsulation le plus léger du langage.

JAVASCRIPT
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    reset: () => { count = 0; },
  };
}
const counterA = createCounter();
const counterB = createCounter();
counterA.increment();
counterA.increment();
console.log(counterA.increment()); // 3
console.log(counterB.increment()); // 1
État privé : count n'est accessible que via les méthodes retournées.

Le piège historique du métier : var est rattaché à la fonction englobante, pas au bloc. Dans une boucle, toutes les closures capturent alors la même variable. Avec let, chaque itération crée un nouvel environnement lexical, donc une capture distincte. Ajoutez à cela la zone morte temporelle (TDZ) : une variable let ou const existe dès l'entrée dans le bloc mais lève une ReferenceError si vous la lisez avant sa déclaration.

JAVASCRIPT
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0); // 0, 1, 2
}
var partage une seule liaison ; let en crée une par itération.

Côté patterns, la closure est le socle de la mémoïsation et du module pattern. Comparée à une classe, elle offre une confidentialité réelle sans this ni prototype, au prix d'une consommation mémoire légèrement supérieure quand vous créez des milliers d'instances : chaque instance porte ses propres fonctions.

JAVASCRIPT
function memoize(fn) {
  const cache = new Map();
  return function (key) {
    if (cache.has(key)) return cache.get(key);
    const result = fn(key);
    cache.set(key, result);
    return result;
  };
}
const square = (n) => n * n;
const fastSquare = memoize(square);
console.log(fastSquare(9)); // calculé
console.log(fastSquare(9)); // lu dans le cache
Le cache vit dans la closure, invisible depuis l'extérieur.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Dans une boucle for (var i = ...), que journalisent trois callbacks setTimeout créés dans le corps de la boucle ?
    • 0, 1, 2 car chaque callback capture sa propre valeur
    • 3, 3, 3 car tous partagent la même liaison de i
    • undefined trois fois car i est détruit après la boucle
  2. Que conserve exactement une closure sur son environnement de définition ?
    • Une copie figée des valeurs au moment de la création
    • Une référence vivante vers les variables capturées
    • Uniquement les paramètres de la fonction englobante
    • Rien : la portée est détruite au retour de la fonction
  3. Pourquoi createCounter garantit-il que count reste privé ?
    • Parce que let rend la variable non modifiable
    • Parce que count n'est accessible que via les fonctions retournées, seules à capturer sa portée
    • Parce que le moteur supprime count après le retour de la fabrique