Closures et scope en JavaScript : maîtriser la portée

🏷️ Front-end 📅 12/04/2026 04:40:00 👤 Mezgani Said
Javascript Closures Scope Fonctions Lexical Scope
Closures et scope en JavaScript : maîtriser la portée

Comprendre les closures, la portée lexicale et comment JavaScript gère les variables avec des exemples pratiques.

Les bases du scope en JavaScript

Le scope (portée) en JavaScript définit où les variables peuvent être accessibles. Chaque variable est créée dans un certain contexte et ne peut être utilisée que dans sa portée.

À retenir : Le scope en JavaScript est lexical, ce qui signifie qu'il est déterminé au moment de l'écriture du code, pas au moment de l'exécution.
// Variable globale
const globalVar = 'Je suis globale';

function myFunction() {
  // Variable locale à la fonction
  const localVar = 'Je suis locale';
  console.info(globalVar);  // ✓ Accessible
  console.info(localVar);   // ✓ Accessible
}

console.info(globalVar); // ✓ Accessible
console.info(localVar);  // ✗ ReferenceError

Types de scope : global, fonctionnel, bloc

1. Global scope : Accessible partout

const globalVar = 'Accessible partout';

function func() {
  console.info(globalVar);
}

2. Function scope : Limité à la fonction

function outer() {
  const funcVar = 'Je suis dans outer';

  function inner() {
    console.info(funcVar); // ✓ Accessible
  }
  inner();
}

console.info(funcVar); // ✗ ReferenceError

3. Block scope (ES6+) : let et const

if (true) {
  let blockVar = 'Je suis dans le bloc';
  var oldVar = 'Je suis une var';
}

console.info(blockVar); // ✗ ReferenceError (block scope)
console.info(oldVar);   // ✓ Accessible (function scope)

Qu'est-ce qu'une closure ?

Une closure est une fonction qui a accès aux variables de sa fonction parent, même après que la fonction parent ait terminé son exécution. C'est une caractéristique fondamentale de JavaScript.

À retenir : Une closure est formée automatiquement quand une fonction interne référence une variable externe. JavaScript conserve l'accès à ces variables.
function createCounter() {
  let count = 0; // Variable de la fonction parent

  return function increment() {
    count++;
    console.info(count);
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

Exemples pratiques de closures

Exemple 1 : Factory function

function createUser(name, age) {
  // Variables privées
  let _name = name;
  let _age = age;

  // Retourner une closure
  return {
    getName() {
      return _name;
    },
    getAge() {
      return _age;
    },
    setAge(newAge) {
      if (newAge > 0) _age = newAge;
    }
  };
}

const user = createUser('Alice', 25);
console.info(user.getName()); // Alice
user.setAge(26);
console.info(user.getAge());  // 26

Exemple 2 : Encapsulation data

function createBankAccount(initialBalance) {
  let balance = initialBalance;

  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount <= balance) {
        balance -= amount;
        return balance;
      }
      return 'Solde insuffisant';
    },
    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(100);
account.deposit(50);  // 150
account.withdraw(30); // 120

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

Quand JavaScript cherche une variable, il parcourt la scope chain : scope local → parent → global.

const globalVar = 'Global';

function outer() {
  const outerVar = 'Outer';

  function middle() {
    const middleVar = 'Middle';

    function inner() {
      const innerVar = 'Inner';

      console.info(innerVar);   // ✓ Trouve dans 'inner'
      console.info(middleVar);  // ✓ Remonte à 'middle'
      console.info(outerVar);   // ✓ Remonte à 'outer'
      console.info(globalVar);  // ✓ Remonte au global
    }

    inner();
  }

  middle();
}

outer();

Patterns courants avec les closures

Pattern 1 : Module pattern

const calculator = (function() {
  // Variables privées
  const PI = 3.14159;

  return {
    circleArea(radius) {
      return PI * radius * radius;
    },
    circumference(radius) {
      return 2 * PI * radius;
    }
  };
})();

console.info(calculator.circleArea(5));     // 78.5...
console.info(calculator.PI);                // undefined (privé)

Pattern 2 : Fonction currifiée

function multiply(a) {
  return function(b) {
    return a * b;
  };
}

const times5 = multiply(5);
console.info(times5(3)); // 15
console.info(times5(4)); // 20

Pattern 3 : Debounce avec closure

function debounce(func, delay) {
  let timerId;

  return function(...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => func(...args), delay);
  };
}

const search = debounce(query => {
  console.info('Recherche :', query);
}, 300);

Pièges et bonnes pratiques

Piège 1 : Variable mutante dans une boucle

// ❌ MAUVAIS
const funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(() => console.info(i));
}
funcs[0](); // 3 (pas 0!)

// ✅ BON - utiliser let
const funcs = [];
for (let i = 0; i < 3; i++) {
  funcs.push(() => console.info(i));
}
funcs[0](); // 0

Piège 2 : Fuite mémoire

// ❌ Potentiel leaking
function attachListener(id) {
  const largeData = new Array(1000000);

  const element = document.getElementById(id);
  element.addEventListener('click', () => {
    console.info(largeData.length); // Garde largeData en mémoire
  });
}

Bonnes pratiques :

  • Utilisez let et const au lieu de var
  • Soyez conscient des variables capturées dans les closures
  • Nettoyez les listeners quand ce n'est plus utile
  • Utilisez les closures pour l'encapsulation, pas pour compliquer le code