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.
// 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.
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
letetconstau lieu devar - 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