Avancé30 min de lecture

Portée & Closures

Comprends comment fonctionne la portée en JavaScript — portée globale, de fonction et de bloc — et maîtrise le concept puissant des closures.

Portée globale & de fonction

La portée détermine où les variables sont accessibles dans ton code. JavaScript possède plusieurs types de portée, et les comprendre est essentiel pour écrire du code sans bugs.

Portée globale — les variables déclarées en dehors de toute fonction ou bloc sont accessibles globalement :

javascript
const appName = 'Kodojo'; // portée globale

function greet() {
  console.log(appName); // accessible ici
}

console.log(appName); // accessible ici aussi

Les variables globales sont disponibles partout, mais en avoir trop pollue l'espace de noms global et peut causer des conflits de nommage. Évite de créer des globales inutiles.

Portée de fonction — les variables déclarées avec var à l'intérieur d'une fonction ne sont accessibles qu'au sein de cette fonction :

javascript
function calculate() {
  var result = 42;
  console.log(result); // 42
}

calculate();
console.log(result); // ReferenceError: result is not defined

var a une portée de fonction — elle ne se soucie pas des blocs comme if ou for, seulement des fonctions :

javascript
function example() {
  if (true) {
    var x = 10; // portée de fonction, pas de bloc !
  }
  console.log(x); // 10 — x est accessible en dehors du bloc if
}

C'est l'une des raisons pour lesquelles let et const ont été introduits — ils se comportent de manière plus prévisible.

Portée de bloc

let et const ont une portée de bloc — ils ne sont accessibles qu'à l'intérieur des accolades {} les plus proches :

javascript
if (true) {
  let x = 10;
  const y = 20;
  console.log(x, y); // 10 20
}
console.log(x); // ReferenceError
console.log(y); // ReferenceError

Cela s'applique à n'importe quel bloc — if, for, while, ou même un {} autonome :

javascript
{
  let secret = 'hidden';
}
console.log(secret); // ReferenceError

La portée de bloc est particulièrement importante dans les boucles for. Avec let, chaque itération obtient sa propre copie de la variable :

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Affiche : 0, 1, 2 (correct !)

Avec var, toutes les itérations partagent la même variable — un piège classique de JavaScript :

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Affiche : 3, 3, 3 (faux !)

C'est pourquoi le JavaScript moderne préfère fortement let et const à var.

La chaîne de portée

Quand JavaScript rencontre une variable, il la recherche à travers la chaîne de portée — en commençant par la portée la plus interne et en remontant vers l'extérieur :

javascript
const global = 'I am global';

function outer() {
  const outerVar = 'I am outer';

  function inner() {
    const innerVar = 'I am inner';
    console.log(innerVar);  // trouvée dans la portée interne
    console.log(outerVar);  // trouvée dans la portée externe
    console.log(global);    // trouvée dans la portée globale
  }

  inner();
}

outer();

L'ordre de recherche est : portée interneportée externeportée globale. Si la variable n'est trouvée nulle part, JavaScript lève une ReferenceError.

Cela signifie que les fonctions internes ont toujours accès aux variables de leurs fonctions parentes. C'est la fondation des closures.

Le masquage de variable se produit quand une portée interne déclare une variable avec le même nom qu'une variable externe :

javascript
const x = 'outer';

function example() {
  const x = 'inner'; // masque le x externe
  console.log(x);    // 'inner'
}

example();
console.log(x); // 'outer' — inchangé

Le x interne cache (masque) le x externe dans cette portée. Le x externe reste intact en dehors de la fonction.

Closures

Une closure est une fonction qui se souvient et peut accéder aux variables de sa portée externe (englobante), même après que la fonction externe a fini son exécution. Les closures sont créées chaque fois qu'une fonction est créée.

javascript
function createGreeter(greeting) {
  // greeting est capturé par la closure
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');

console.log(sayHello('Alice')); // 'Hello, Alice!'
console.log(sayHi('Bob'));      // 'Hi, Bob!'

Bien que createGreeter ait terminé son exécution, sayHello et sayHi se souviennent encore de leurs valeurs respectives de greeting. C'est la closure en action.

Cas d'usage courants des closures :

  1. Confidentialité des données — cacher des variables de l'accès extérieur :

    javascript
    function makeCounter() {
      let count = 0; // privé !
      return {
        increment() { count++; },
        getCount() { return count; },
      };
    }
  2. Fonctions factory — créer des fonctions spécialisées :

    javascript
    function multiplier(factor) {
      return (n) => n * factor;
    }
    const double = multiplier(2);
    const triple = multiplier(3);
  3. Callbacks qui se souviennent du contexte — gestionnaires d'événements, timers, etc.

  4. Application partielle — pré-remplir certains arguments d'une fonction.

Les closures sont l'un des patterns les plus puissants en JavaScript. Elles permettent les modules, l'état privé et les techniques de programmation fonctionnelle.

Closures en action

html
<p id="output"></p>

<script>
  // A counter factory using closures
  function makeCounter() {
    let count = 0; // private variable

    return {
      increment() {
        count++;
      },
      decrement() {
        count--;
      },
      getCount() {
        return count;
      },
    };
  }

  const counter = makeCounter();
  counter.increment();
  counter.increment();
  counter.increment();
  counter.decrement();

  // count is not accessible directly:
  // console.log(counter.count); // undefined

  // Only through the closure:
  document.getElementById('output').textContent =
    'Count: ' + counter.getCount(); // Count: 2
</script>

Qu'est-ce qu'une closure ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.