Closures et portée en JavaScript expliqués simplement

Frontend2026-09-19TryQuickToolBox

Vous écrivez une fonction JavaScript qui en retourne une autre. La fonction interne utilise une variable de la fonction externe, mais lorsque vous l'appelez plus tard, la fonction externe a déjà terminé. Pourtant, la variable est toujours là. Comment ? C'est la magie des closures et de la portée. De nombreux développeurs ont du mal avec ce concept, ce qui entraîne des bugs comme des valeurs de variables inattendues ou des fuites de mémoire. Dans cet article, nous allons décomposer les closures et la portée en JavaScript en termes simples avec des exemples pratiques.

Qu'est-ce que la portée en JavaScript ?

La portée détermine où les variables sont accessibles dans votre code. JavaScript a trois principaux types de portée :

Voici un exemple rapide :

let globalVar = 'I am global';

function myFunc() {
  let functionVar = 'I am in function scope';
  if (true) {
    let blockVar = 'I am in block scope';
    console.log(globalVar); // accessible
    console.log(functionVar); // accessible
    console.log(blockVar); // accessible
  }
  console.log(blockVar); // ReferenceError: blockVar is not defined
}

myFunc();
console.log(functionVar); // ReferenceError: functionVar is not defined

Comprendre la portée est crucial car cela évite les collisions de variables et aide à gérer la mémoire.

Lexical Scoping : La base des closures

JavaScript utilise le lexical scoping, ce qui signifie que la portée d'une variable est déterminée par sa position dans le code source. Les fonctions internes ont accès aux variables de leur portée externe (englobante). C'est aussi appelé static scoping.

Considérez ceci :

function outer() {
  let outerVar = 'outer';
  function inner() {
    console.log(outerVar); // 'outer'
  }
  inner();
}
outer();

La fonction inner peut accéder à outerVar car elle est lexicalement à l'intérieur de outer. Ce n'est pas dynamique ; cela ne dépend pas de la façon dont la fonction est appelée.

Qu'est-ce qu'une closure ?

Une closure est une fonction qui se souvient de sa portée lexicale même lorsqu'elle est exécutée en dehors de cette portée. En d'autres termes, une closure vous donne accès à la portée d'une fonction externe depuis une fonction interne. Les closures sont créées à chaque fois qu'une fonction est créée, au moment de la création de la fonction.

Exemple :

function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Ici, makeCounter retourne une fonction interne qui incrémente count. Même après que makeCounter a fini de s'exécuter, la fonction retournée a toujours accès à count. C'est une closure.

Comment fonctionnent les closures sous le capot

Lorsqu'une fonction est créée, elle stocke une référence à son environnement lexical (la chaîne de portée). Lorsque la fonction est invoquée, elle utilise cet environnement pour résoudre les variables. L'environnement est maintenu en vie aussi longtemps que la closure existe.

Utilisations pratiques des closures

Les closures ne sont pas seulement théoriques ; elles sont utilisées partout en JavaScript.

1. Confidentialité des données (Module Pattern)

Avant les modules ES6, les closures étaient utilisées pour créer des variables privées :

const counterModule = (function() {
  let count = 0;
  return {
    increment: function() { count++; },
    getCount: function() { return count; }
  };
})();

counterModule.increment();
console.log(counterModule.getCount()); // 1
console.log(counterModule.count); // undefined

Ici, count est privé et accessible uniquement via les méthodes retournées.

2. Gestionnaires d'événements et callbacks

Les closures sont couramment utilisées dans les gestionnaires d'événements pour accéder aux variables de la portée externe :

function setupButton() {
  let clickCount = 0;
  document.getElementById('myButton').addEventListener('click', function() {
    clickCount++;
    console.log(`Button clicked ${clickCount} times`);
  });
}

La closure du gestionnaire d'événements se souvient de clickCount.

3. Fabriques de fonctions

Vous pouvez créer des fonctions avec des arguments prédéfinis :

function multiplyBy(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplyBy(2);
console.log(double(5)); // 10

Pièges courants et comment les éviter

Les closures peuvent entraîner un comportement inattendu si elles ne sont pas comprises.

Capture de variable dans une boucle

Utiliser var dans les boucles crée une seule liaison partagée :

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // prints 3, 3, 3
  }, 100);
}

Tous les callbacks partagent le même i. Pour corriger, utilisez let (portée de bloc) ou un IIFE :

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // prints 0, 1, 2
  }, 100);
}

Fuites de mémoire

Les closures gardent les variables en vie. Si une closure est attachée à un objet à longue durée de vie (comme un élément DOM), elle peut empêcher le ramasse-miettes. Supprimez toujours les écouteurs d'événements lorsqu'ils ne sont plus nécessaires.

Chaîne de portée et résolution des variables

Lorsque vous accédez à une variable, JavaScript parcourt la chaîne de portée : d'abord la portée actuelle, puis les portées externes, jusqu'à la portée globale. Si elle n'est pas trouvée, une ReferenceError est levée.

Exemple :

let a = 'global';
function outer() {
  let b = 'outer';
  function inner() {
    let c = 'inner';
    console.log(a, b, c); // all accessible
  }
  inner();
}
outer();

La chaîne de portée pour inner est : inner -> outer -> global.

Comparaison : var, let et const

Mot-cléPortéeHoistingRéassignable
varFonctionOui (initialisé à undefined)Oui
letBlocOui (mais non initialisé, TDZ)Oui
constBlocOui (mais non initialisé, TDZ)Non

Comprendre ces différences est essentiel pour éviter les bugs liés à la portée.

FAQ

Qu'est-ce qu'une closure en JavaScript ?

Une closure est une fonction qui conserve l'accès à sa portée lexicale même lorsqu'elle est exécutée en dehors de cette portée. Elle permet à une fonction de « se souvenir » des variables de l'endroit où elle a été définie.

Pourquoi les closures sont-elles importantes ?

Les closures permettent la confidentialité des données, les fabriques de fonctions et les gestionnaires d'événements qui maintiennent un état. Elles sont fondamentales pour de nombreux patterns JavaScript.

Comment puis-je éviter les bugs liés aux closures ?

Utilisez let et const pour la portée de bloc, soyez attentif aux boucles et nettoyez les écouteurs d'événements pour éviter les fuites de mémoire.

Prêt à tester votre compréhension ? Essayez notre JSON Formatter pour expérimenter avec des extraits de code et voir comment les closures fonctionnent en temps réel.