Pourquoi closures en JavaScript ?
Les closures en JavaScript sont essentielles car elles permettent aux fonctions d'accéder et de manipuler les variables déclarées dans leur portée parente, même après que cette fonction parente n'ait terminé d'exécuter. Cette fonctionnalité est cruciale pour créer des fonctions avec une mémoire persistante et pour concevoir des patterns comme le singleton, le module et la curryfication.
Un cas concret de closure en JavaScript est la création d'une fonction qui maintient un état interne entre les appels successifs. Par exemple, si vous créez une fonction qui incrémente un compteur à chaque appel, cette fonction devrait avoir accès au compteur même après que l'initialisation ait été effectuée.
Prerequis
- Comprendre les concepts de portées (scope) et de variables en JavaScript.
- Savoir comment déclarer des fonctions en JavaScript.
- Avoir une connaissance de base des opérations sur les types de données en JavaScript.
Concepts fondamentaux
1. Portée et Variables
Les closures sont basées sur la portée des variables. En JavaScript, il existe trois niveaux de portée : le global, le bloc (à partir d'ES6 avec let et const) et la fonction.
var x = 10;
function outerFunction() {
# Exemple de portée fonctionnelle
var y = 20;
function innerFunction() {
# Exemple de portée bloc avec let/const (ES6)
const z = 30;
console.log(x, y, z); // Accessible car dans la même portée
}
return innerFunction; // Retourne la fonction interne
}
##
var myClosure = outerFunction();
myClosure(); // Affiche 10 20 30
2. Fonction interne accédant aux variables externes
L'une des caractéristiques majeures d'une closure est qu'elle peut accéder à la portée de sa fonction parente.
function createCounter() {
let count = 0; // Variable locale à createCounter
function increment() { // Fonction interne qui accède à count
count++;
return count;
}
return increment; // Retourne la fonction interne
}
##
const counter = createCounter();
console.log(counter()); // Affiche 1
console.log(counter()); // Affiche 2
3. Closures et Memory Management
Les closures sont utilisées pour maintenir des variables en mémoire même après que la fonction parente n'ait terminé d'exécuter.
function createTimer() {
let startTime = Date.now();
function getTimePassed() {
return (Date.now() - startTime) + 'ms';
}
return getTimePassed;
}
const timer = createTimer();
console.log(timer()); // Affiche le temps écoulé depuis l'appel de createTimer
Mise en pratique : projet fil rouge
Étape 1: Définition des structures de données et fonctions principales
mkdir counter-app
cd counter-app
npm init -y
Créez un fichier index.js avec le contenu suivant :
// index.js
const fs = require('fs');
const readline = require('readline');
function createCounter() {
let count = 0;
function increment() {
count++;
return count;
}
function decrement() {
count--;
return count;
}
function getCount() {
return count;
}
return { increment, decrement, getCount };
}
const counter = createCounter();
console.log(`Current count: ${counter.getCount()}`);
counter.increment();
console.log(`After increment: ${counter.getCount()}`);
counter.decrement();
console.log(`After decrement: ${counter.getCount()}`);
Étape 2: Ajout de l'interface utilisateur avec readline
Créez un fichier cli.js avec le contenu suivant :
// cli.js
const counter = require('./index');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function showMenu() {
console.log('1. Increment');
console.log('2. Decrement');
console.log('3. Get Count');
console.log('4. Exit');
}
async function main() {
while (true) {
showMenu();
const choice = await new Promise(resolve => rl.question('Choose an option: ', resolve));
switch (choice) {
case '1':
counter.increment();
break;
case '2':
counter.decrement();
break;
case '3':
console.log(`Current count: ${counter.getCount()}`);
break;
case '4':
rl.close();
return;
default:
console.log('Invalid option. Please try again.');
}
}
}
main().catch(console.error);
Étape 3: Ajout de la gestion des erreurs
Dans index.js, ajoutez une gestion des erreurs pour les opérations sur le compteur :
// index.js
const fs = require('fs');
const readline = require('readline');
function createCounter() {
let count = 0;
function increment() {
if (count < 10) {
count++;
} else {
throw new Error('Count cannot exceed 10');
}
return count;
}
function decrement() {
if (count > 0) {
count--;
} else {
throw new Error('Count cannot be less than 0');
}
return count;
}
function getCount() {
return count;
}
return { increment, decrement, getCount };
}
const counter = createCounter();
try {
console.log(`Current count: ${counter.getCount()}`);
counter.increment();
console.log(`After increment: ${counter.getCount()}`);
counter.decrement();
console.log(`After decrement: ${counter.getCount()}`);
} catch (error) {
console.error(error.message);
}
rl.question('Press enter to exit...', () => process.exit());
Étape 4: Exécution du projet
node cli.js
Ce mini-projet complet permet de créer une application en ligne de commande qui utilise une closure pour maintenir un état interne et gérer des erreurs. Les utilisateurs peuvent incrémenter, décrémenter et obtenir la valeur actuelle du compteur.
Erreurs frequentes et debugging
1. Erreur : Uncaught ReferenceError: count is not defined
function createCounter() {
let count = 0;
function increment() {
count++; // Erreur ici : count n'est pas défini dans la portée de increment
return count;
}
return increment;
}
javascript##
function createCounter() {
let count = 0;
function increment() {
count++;
return count;
}
return { increment }; // Retourne l'objet avec la méthode increment
}
2. Erreur : Uncaught TypeError: Cannot read property 'getCount' of undefined
const counter = createCounter();
console.log(counter.getCount()); // Erreur ici : getCount n'est pas une fonction de counter
javascript##
const counter = createCounter();
console.log(counter.getCount()); // Affiche la valeur actuelle du compteur
3. Erreur : Uncaught Error: Count cannot exceed 10
counter.increment();
counter.increment();
counter.increment();
counter.increment();
counter.increment(); // Erreur ici : count dépasse 10
javascript##
try {
counter.increment();
counter.increment();
counter.increment();
counter.increment();
counter.increment();
} catch (error) {
console.error(error.message); // Affiche "Count cannot exceed 10"
}
Pour aller plus loin
1. Understanding the Event Loop in JavaScript
- En savoir plus sur le cycle d'événements en JavaScript pour comprendre comment les closures fonctionnent avec des opérations asynchrones.
- MDN Web Docs: The Event Loop
2. Functional Programming in JavaScript
- Découvrir les principes de la programmation fonctionnelle en JavaScript et comment elles peuvent être appliquées avec des closures.
- MDN Web Docs: Higher-order functions
3. Advanced JavaScript Concepts
- Explorer d'autres concepts avancés de JavaScript tels que les prototypage, les modules ES6 et les classes pour approfondir votre compréhension des closures.
- MDN Web Docs: JavaScript advanced topics
Défi pratique
Créez une application de gestion de notes en utilisant des closures. L'application devrait permettre aux utilisateurs d'ajouter, de supprimer et de récupérer les notes. Implémentez la gestion des erreurs pour les opérations sur les notes.