Les Fonctions de Closures en JavaScript : (Closure Function)
Les Closures sont l’un des concepts les plus intéressants et les plus subtils en JavaScript.
Elles peuvent parfois sembler complexes à comprendre et dans quel contexte les utiliser.
Voici un article qui va déstructurer enfin ce concept à savoir :
Que ce qu’est une closure, comment elle fonctionne, et comment l’utiliser efficacement.
Qu’est-ce qu’une Closure ?
Une closure est une fonction qui capture et conserve les variables de son environnement lexical au moment où elle est créée.
En clair, une closure « se souvient » du contexte dans lequel elle a été définie, même si elle est exécutée en dehors de ce contexte.
On utilise avec une closure avec une fonction anonyme à l’intérieur d’une fonction nommée (voir les autres articles sur les fonctions).
Tout d’abord Le Contexte Lexical
Pour bien comprendre les closures, il est crucial de comprendre ce qu’est un contexte lexical.
En JavaScript, le contexte lexical fait référence à la manière dont les variables et les fonctions sont organisées et accessibles dans le code en fonction de leur emplacement (leur position) dans le code source.
Lorsque vous définissez une fonction dans JavaScript, elle capture le contexte lexical où elle a été créée, c’est-à-dire toutes les variables et fonctions accessibles à ce moment-là.
Ce contexte reste attaché à la fonction même lorsqu’elle est exécutée en dehors de sa portée d’origine.
Exemple de Closure en Action
Voyons un exemple simple pour illustrer cela :
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // Affiche 1
console.log(counter1()); // Affiche 2
console.log(counter1()); // Affiche 3
Explication du code ci dessus :
Définition de
createCounter
: La fonctioncreateCounter
crée une variable localecount
et retourne une fonction anonyme.Création de la Closure : Lorsque
createCounter
est appelée, elle retourne une nouvelle fonction qui a accès à la variablecount
définie danscreateCounter
. Cette fonction anonyme est la closure.Maintien de l’État : Même après que
createCounter
a terminé son exécution, la fonction retournée (la closure) conserve une référence à la variablecount
. À chaque appel decounter1()
, la variablecount
est incrémentée.
Maintenant Pourquoi les Closures Sont-elles Utiles ?
Les closures offrent plusieurs avantages dans la programmation JavaScript :
Encapsulation : Elles permettent de cacher l’état interne d’une fonction. Par exemple, dans l’exemple précédent, la variable
count
n’est pas accessible directement depuis l’extérieur, ce qui garantit que l’état ne peut être modifié qu’à travers la closure.Fonctions d’usine (Factory Functions) : Les closures sont couramment utilisées pour créer des fonctions d’usine qui génèrent des fonctions personnalisées selon des paramètres.
Fonctions Callback : Elles sont très utilisées dans les callbacks et les gestionnaires d’événements pour capturer l’état au moment où le callback est défini.
Gestion de l’état : Elles permettent de maintenir un état persistant dans des environnements où cela pourrait autrement être difficile, comme dans les boucles ou les gestionnaires d’événements.
Voici des Exemples plus Avancés de Closures
Exemple 1 : Utiliser des Closures pour Capturer des Variables de Boucle
Les closures peuvent être utilisées pour capturer l’état d’une variable dans une boucle. Sans closure, cela peut conduire à des comportements inattendus.
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
// Cela affichera 6 cinq fois, car `i` est capturé par référence et est égal à 6 après la boucle.
i
à chaque itération :
for (var i = 1; i <= 5; i++) {
(function(x) {
setTimeout(function() {
console.log(x);
}, x * 1000);
})(i);
}
// Cela affichera 1, 2, 3, 4, 5 respectivement.
Exemple 2 : Créer des Fonctions d’usine avec des Closures
Les fonctions d’usine sont des fonctions qui retournent d’autres fonctions configurées de manière spécifique. Les closures sont parfaites pour cela.
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const doubler = createMultiplier(2);
const tripler = createMultiplier(3);
console.log(doubler(5)); // Affiche 10
console.log(tripler(5)); // Affiche 15
createMultiplier
utilise une closure pour capturer la valeur de factor
. Les fonctions doubler
et tripler
« se souviennent » du facteur avec lequel elles ont été créées.Voici un Exemple sans Closure et les Erreurs Générées
Imaginons que nous ayons un scénario où nous voulons créer plusieurs boutons sur une page web, et que lorsque nous cliquons sur un bouton, il affiche son index (c’est-à-dire sa position dans la liste des boutons).
Sans Closure : Utilisation d’une Variable Globale
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exemple Sans Closure</title>
</head>
<body>
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
<script>
var buttons = document.querySelectorAll('button');
for (var index = 0; index < buttons.length; index++) {
buttons[index].addEventListener('click', function() {
console.log("Button index : " + index);
});
}
</script>
</body>
</html>
Explication
- Variable Globale
index
: Ici, nous avons utilisé une variable globaleindex
pour stocker l’index du bouton. - Problème : Lorsque vous cliquez sur n’importe quel bouton, vous verrez toujours la même valeur affichée. Ce comportement se produit parce que la variable globale
index
est partagée entre toutes les fonctions de gestion d’événements, et lorsque la bouclefor
se termine, la valeur deindex
est celle du dernier index de la boucle (dans ce cas,3
, carindex
est incrémenté après la dernière itération).
Effectivement Ce Résultat est Une Erreur Logique
Si vous cliquez sur n’importe quel bouton, vous verrez Button index : 3
pour tous les boutons, ce qui est incorrect.
L’erreur est due au fait que toutes les fonctions d’événements se réfèrent à la même variable globale index
, qui a été modifiée lors de l’exécution de la boucle.
Avec une Closure : Nous Résolvons ce Problème
Maintenant, voyons comment faire pour utiliser une closure pour résoudre ce problème :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exemple Avec Closure</title>
</head>
<body>
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
<script>
var buttons = document.querySelectorAll('button');
for (var index = 0; index < buttons.length; index++) {
(function(i) {
buttons[i].addEventListener('click', function() {
console.log("Button a index : " + i);
});
})(index);
}
</script>
</body>
</html>
Explication
- Fonction Anonyme Immédiatement Invoquée (IIFE) : Nous avons utilisé une IIFE (Immediately Invoked Function Expression) pour créer une closure. Cette fonction est exécutée immédiatement et capture la valeur de
index
pour chaque itération. - Variable
i
: La variablei
est passée en argument à l’IIFE, ce qui permet de capturer la valeur actuelle deindex
à chaque tour de boucle.
Le Résultat est un Comportement Correct
Maintenant, si vous cliquez sur les boutons, vous verrez :
Button index : 0
pour le premier bouton,Button index : 1
pour le deuxième bouton,Button index : 2
pour le troisième bouton.
Cependant autre méthode sans var
En utilisant let
au lieu de var
pour déclarer la variable d’index dans la boucle, vous pouvez résoudre ce problème sans avoir besoin d’utiliser de closures.
C’est parce que let
a une portée de bloc (block scope), contrairement à var
qui a une portée de fonction (function scope).
Exemple avec let
au lieu de var
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exemple avec let</title>
</head>
<body>
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
<script>
var buttons = document.querySelectorAll('button');
for (let index = 0; index < buttons.length; index++) {
buttons[index].addEventListener('click', function() {
console.log("Button index : " + index);
});
}
</script>
</body>
</html>
Explication
- Portée de Bloc avec
let
: Lorsque vous utilisezlet
, la variableindex
est réinitialisée à chaque itération de la boucle. Cela signifie que chaque gestionnaire d’événements se voit attribuer une nouvelle instance deindex
qui est spécifique à cette itération de la boucle.
Le Comportement est Correct
Tout comme avec la solution utilisant une closure, si vous cliquez sur les boutons, vous verrez :
Button index : 0
pour le premier bouton,Button index : 1
pour le deuxième bouton,Button index : 2
pour le troisième bouton.
let
dans une boucle for
est une solution simple et élégante pour le problème de portée des variables sans avoir besoin de recourir aux closures.let
, chaque itération de la boucle dispose de sa propre instance de la variable, ce qui résout le problème des références incorrectes dans les gestionnaires d’événements.Bien que les closures soient extrêmement nécessaires dans de nombreux autres cas, dans ce cas précis, l’utilisation de let
offre une solution plus directe à ce problème.
Dans Quelle autre Cas ?
Les closures sont utiles pour plusieurs raisons, même lorsque des alternatives comme l’utilisation de let
sont disponibles pour certains cas spécifiques.
Les closures offrent des capacités uniques qui vont bien au-delà de la simple gestion de la portée des variables dans des boucles.
Voici pourquoi les closures restent essentielles et des situations où elles sont particulièrement utiles :
1. Maintien de l’État Privé
Les closures permettent de créer des fonctions avec un état privé qui persiste entre les appels.
Contrairement à des variables globales ou à portée de bloc, cet état est encapsulé dans la fonction et ne peut être modifié que par cette fonction.
Exemple : Compteur avec État Privé
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // Affiche 1
console.log(counter1()); // Affiche 2
console.log(counter1()); // Affiche 3
const counter2 = createCounter();
console.log(counter2()); // Affiche 1 (compteur distinct)
count
est encapsulé dans la closure, ce qui permet à counter1
et counter2
de maintenir leur propre état indépendamment l’un de l’autre. Cet état ne peut pas être modifié directement depuis l’extérieur, ce qui renforce l’encapsulation.2. Création de Fonctions Dynamiques
Les closures permettent de créer des fonctions qui sont configurées dynamiquement en fonction des variables d’un contexte externe.
Exemple : Fonctions Générées Dynamiquement
function createAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = createAdder(5);
const add10 = createAdder(10);
console.log(add5(2)); // Affiche 7
console.log(add10(2)); // Affiche 12
Preuve d’utilité : La closure ici capture la valeur de x
à chaque fois que createAdder
est appelée, créant ainsi des fonctions spécifiques (add5
, add10
) qui ajoutent un nombre prédéfini à un autre nombre. Cette flexibilité est difficile à obtenir sans closures.
3. Callbacks et Gestion d’Événements
Dans les environnements asynchrones, comme avec les callbacks ou les gestionnaires d’événements, les closures permettent de capturer l’état au moment où le callback est défini.
Exemple : Gestion d’Événements avec Capture d’État
function setupButton() {
let name = "Button 1";
document.querySelector("button").addEventListener("click", function() {
alert("Clicked by " + name);
});
}
setupButton();
Preuve d’utilité : Le gestionnaire d’événements capturé « se souvient » de la valeur de name
au moment où setupButton
a été exécuté. Cette capture est essentielle pour que le comportement soit correct, même si name
était modifié ou réinitialisé par la suite.
4. Fonctions d’Ordre Supérieur
Les closures sont à la base de nombreuses fonctions d’ordre supérieur (fonctions qui prennent d’autres fonctions comme arguments ou retournent des fonctions), un concept clé en programmation fonctionnelle.
Exemple : Utilisation dans map
, filter
, reduce
const numbers = [1, 2, 3, 4, 5];
const doubles = numbers.map(function(n) {
return n * 2;
});
console.log(doubles); // Affiche [2, 4, 6, 8, 10]
map
capture l’état de chaque élément du tableau numbers
. Cette capture permet de transformer chaque élément de manière concise et efficace.A bien retenir :
Les closures sont extrêmement utiles pour encapsuler l’état, créer des fonctions dynamiques, gérer des callbacks, et pour des cas avancés de programmation fonctionnelle.
Elles ne sont pas seulement une solution à un problème de portée de variables dans les boucles (un problème que let
peut résoudre), mais une caractéristique fondamentale qui permet une programmation plus sécurisé, découpé, rèutllisable et fonctionnelle.
Preuve de leur utilité :
Les closures permettent des structures de code qui seraient autrement impossibles ou très compliquées à réaliser.
Elles sont importantes pour gérer l’état privé, pour créer des fonctions d’usine, pour assurer des comportements corrects dans des contextes asynchrones, et pour écrire des fonctions d’ordre supérieur, qui sont des piliers de la programmation moderne en JavaScript.
Améliore ton bagage dans l’apprentissage de JavaScript pour cela :
Je t’offre un Guide Bonus Exclusif
En allant plus loin, avec ce Guide Bonus Exclusif rien que pour Toi !
Voici un guide complet sur le JavaScript, où tu verras des techniques pour performer en programmation Js.
Ce guide te permettra de perfectionner tes compétences et de devenir un expert JavaScript. Ne le rate pas et développe ton expertise !
En adoptant ce qu’il contient, tu rends ton apprentissage de JavaScript plus performant avec une plus grande facilité tous les jours . Voici de quoi enrichir tout de suite ton savoir-faire ? Le guide complet t’attend !
Quelques liens en supplément de ce cours :
https://developpeur-pro.com/cours-javascript-les-bases
Rejoignez notre Newsletter et Restez Informé !
Vous souhaitez rester à jour avec les dernières tendances et actualités du monde du développement et le métier de développeur. Comment devenir développeur pro ? Rejoignez notre newsletter pour obtenir un accès exclusif à du contenu premium, des astuces de codage, des mises à jour sur les nouvelles fonctionnalités et bien plus encore !
Avantages de l’Inscription
- Restez Informé: Recevez des articles informatifs sur les dernières avancées et les meilleures pratiques de codage et les softkills.
- Promos Exclusives: Accédez à des formations détaillés et à des exemples de code pour améliorer vos compétences en programmation.
- Aperçus des Nouveautés: Soyez parmi les premiers à découvrir les nouvelles fonctionnalités et les frameworks émergents dans l’écosystème du développement FrontEnd et Backend.
- Communauté Engagée: Rejoignez une communauté passionnée de développeurs et partagez vos idées, questions et expériences.
Comment S’Inscrire
C’est simple et rapide ! Remplissez le formulaire d’inscription avec votre adresse e-mail et cliquez sur « S’Inscrire ». Vous recevrez régulièrement notre newsletter dans votre boîte de réception.
L’inscription à notre newsletter est un moyen idéal de rester informé et de progresser dans le domaine de la programmation et du développement pour devenir un développeur professionnel ou une développeuse pro.