Cet article explore les trois piliers qui déterminent comment JavaScript gère vos variables dans le temps et dans l'espace (mémoire).
- Le Scope (La Portée) :
Le Territoire des Variables
Le Scope définit la zone de code où une variable est "vivante". Si vous essayez d'appeler une variable en dehors de son scope, JavaScript renverra une ReferenceError.
A. Global Scope (Le Domaine Public)
Une variable déclarée hors de toute fonction ou bloc {}.
- Accès : Partout dans votre programme.
- Danger : Trop de variables globales polluent la mémoire et risquent des collisions de noms (deux variables avec le même nom).
B. Function Scope (Le Domaine Privé)
Une variable déclarée dans une function.
- Accès : Uniquement à l'intérieur de cette fonction.
- Note : Le mot-clé var respecte ce scope.
C. Block Scope (La Cellule) - Depuis ES6
Une variable déclarée avec let ou const à l'intérieur d'accolades { } (if, for, while).
- Accès : Uniquement dans ce bloc.
- La différence cruciale : var ne connaît pas le Block Scope. Si vous mettez un var dans un if, il sera visible en dehors du if. let, lui, reste enfermé.
- Synchrone vs Asynchrone :
La Gestion du Temps
Pour comprendre les bugs de boucle, il faut comprendre l'Event Loop (la boucle d'événements).
-
Synchrone (Bloquant): JavaScript exécute une ligne, attend qu'elle finisse, puis passe à la suivante. C'est une file d'attente à la caisse d'un supermarché. -
Asynchrone (Non-bloquant): Avec des fonctions comme setTimeout ou fetch, JavaScript lance la tâche, lui donne un "ticket de rappel" (callback), et continue immédiatement son travail. Il reviendra exécuter le rappel plus tard, quand la pile d'exécution principale sera vide.
- La Closure (La Fermeture) : Le
Sac à dos
C'est le concept le plus puissant. Une Closure se produit lorsqu'une fonction "enfant" est créée à l'intérieur d'une fonction parente.
Le mécanisme : La fonction enfant capture l'environnement dans lequel elle a été créée. Elle emporte avec elle un "sac à dos" contenant toutes les variables locales qui étaient présentes à sa naissance. Même si la fonction parente a terminé son exécution et a disparu, l'enfant garde son sac à dos intact.
- Le Garbage Collector et la Mémoire
JavaScript utilise un Garbage Collector (GC). Son rôle est simple : si une donnée n'est plus accessible par aucune variable de votre programme, il la supprime de la RAM.
Le piège des Closures : Comme une closure garde une référence vers les variables de son parent, le GC considère que ces variables sont toujours "utilisées". Si votre sac à dos contient une image de 10 Mo et que vous ne détruisez jamais la fonction enfant, ces 10 Mo resteront bloqués en mémoire pour toujours. C'est une fuite de mémoire.
- EXERCICE DÉTAILLÉ : Le Mystère de l'Usine à Salutations
Demandez à vos étudiants d'analyser ce code qui combine tous les concepts vus plus haut.
function creerUsine() {
// 1. Une donnée très lourde (simule une fuite de mémoire potentielle)
let grossesDonnees = new Array(1000000).fill("💾");
let noms = ["Alice", "Bob", "Charlie"];
let fonctionsDeSalutation = [];
// 2. La Boucle avec le bug classique
for (var i = 0; i < noms.length; i++) {
// On crée une closure ici
fonctionsDeSalutation.push(function() {
console.log("Tentative de saluer : " + noms[i]);
console.log("Taille des données stockées : " + grossesDonnees.length);
});
}
return fonctionsDeSalutation;
}
// 3. Exécutionlet mesSalutations = creerUsine();
// On attend 1 seconde avant de lancer les salutations
setTimeout(() => {
console.log("--- Lancement des fonctions ---");
mesSalutations.forEach(f => f());
}, 1000);
-
🔍 Questions d'analyse pour les étudiants :
- Le Bug du Nom : Pourquoi le programme affiche-t-il Bonjour, undefined trois fois au lieu d'Alice, Bob et Charlie ?
- Explication attendue : var n'a pas de block scope. À la fin de la boucle (synchrone),
ivaut3. Quand lesetTimeout(asynchrone) se déclenche, les 3 fonctions regardent la même variableiqui vaut3.noms[3]n'existe pas.
- L'impact de la Closure : Pourquoi grossesDonnees est-il toujours accessible dans le console.log alors que la fonction creerUsine est terminée depuis longtemps ?
Explication attendue : C'est le principe de la closure. La fonction anonyme a "capturé" grossesDonnees dans son sac à dos lors de sa création.
La Fuite de Mémoire : Imaginez que vous n'utilisez plus jamais mesSalutations après le premier affichage. Comment forcer le Garbage Collector à supprimer les 1 000 000 d'emojis de la mémoire ?
Réponse : mesSalutations = null;. Cela coupe le dernier lien vers les fonctions, ce qui permet au GC de vider les
sacs à dos.La Correction : Comment corriger le bug du nom avec un seul mot-clé ?
Réponse : Remplacer var i par let i. Cela crée une nouvelle variable i pour chaque itération (chaque fonction aura sa propre "photo" de i).
Cet exercice montre bien que la puissance des closures (garder des données) est aussi leur point faible (consommation mémoire).
Top comments (2)
You've missed Module scope. Also, nesting of functions is not required for the creation of closures - a closure is created every time ANY function is created.
Misconceptions About Closures
Thanks for the clarification. Most articles are wrong then.😅