Aller au contenu principal

[Hard] 📄 Closure

1. What is Closure ?​

Qu'est-ce qu'une Closure ?

Pour comprendre les closures, il faut d'abord comprendre la portée des variables en JavaScript et comment les fonctions accÚdent aux variables externes.

Variable Scope (PortĂ©e des variables)​

En JavaScript, la portée des variables se divise en deux types : global scope et function scope.

// global scope
let a = 1;

function parentFunction() {
// function scope
let b = 2;

function childFunction() {
let c = 3;
console.log(a, b, c); // print 1 2 3, can access global scope & function scope
}

childFunction();
}

parentFunction();
console.log(a); // print 1, can access global scope
console.log(b, c); // Erreur générée, impossible d'accéder aux variables dans le function scope

Closure example​

La condition de déclenchement d'une Closure est qu'une fonction enfant soit définie à l'intérieur d'une fonction parent et retournée via return, permettant de conserver les variables d'environnement de la fonction enfant (ce qui évite le Garbage Collection).

function parentFunction() {
let count = 0;

return function childFunction() {
count += 1;
console.log(`Compteur actuel : ${count}`);
};
}

const counter = parentFunction();

counter(); // print Compteur actuel : 1
counter(); // print Compteur actuel : 2
// La variable count n'est pas récupérée par le garbage collector, car childFunction existe toujours et met à jour la valeur de count à chaque appel

Cependant, il faut noter que les closures conservent les variables en mémoire. Si les variables sont trop nombreuses, cela peut entraßner une utilisation excessive de la mémoire (il ne faut pas abuser des closures), affectant ainsi les performances.

2. Create a function that meets the following conditions​

Créez une fonction qui remplit les conditions suivantes (en utilisant le concept de closure)

plus(2, 5); // output 7
plus(2)(5); // output 7

First Solution : two functions​

On sépare le traitement en deux fonctions

function plus(value, subValue) {
return value + subValue;
}

console.log(plus(2, 5));
// use closure save variable

function plus(value) {
return function (subValue) {
return value + subValue;
};
}

console.log(plus(2)(5));

Second Solution : single function​

Bien sĂ»r, la premiĂšre solution a de fortes chances d'ĂȘtre rejetĂ©e, il faut donc essayer de tout combiner dans une seule fonction.

function plus(value, subValue) {
// On détermine en fonction du nombre de paramÚtres passés
if (arguments.length > 1) {
return value + subValue;
} else {
return function (item) {
return value + item;
};
}
}

console.log(plus(2, 5));
console.log(plus(2)(5));

3. Please take advantage of the closure feature to increase the number​

Utilisez la caractéristique de closure pour incrémenter un nombre

function plus() {
// code
}

var obj = plus();
obj.add(); // print 1
obj.add(); // print 2

First Solution : return variable​

Ici, on n'utilise pas les Arrow Functions, mais la forme classique de function.

function plus() {
let cash = 0;
let counter = {
add() {
cash += 1;
console.log(cash);
},
};
return counter;
}

var obj = plus();
obj.add();
obj.add();

Second Solution : return object​

Dans la solution précédente, on peut aussi inclure directement l'objet dans le return

function plus() {
let cash = 0;
return {
add: function () {
cash += 1;
console.log(cash);
},
};
}

var obj = plus();
obj.add();
obj.add();

4. What will be printed in this nested function call?​

Qu'affichera cet appel de fonctions imbriquées ?

function a(aa) {
aa();
}

function b(bb) {
bb();
}

function c() {
console.log('hello');
}

a(b(c));

Analyse​

Résultat d'exécution :

hello
TypeError: aa is not a function

Flux d'exĂ©cution dĂ©taillé​

// Exécution de a(b(c))
// JavaScript exécute les fonctions de l'intérieur vers l'extérieur

// Étape 1 : ExĂ©cuter la fonction la plus interne b(c)
b(c)
↓
// La fonction c est passée en paramÚtre à b
// À l'intĂ©rieur de b, bb() est exĂ©cutĂ©, c'est-Ă -dire c()
c() // Affiche 'hello'
↓
// La fonction b n'a pas d'instruction return
// Elle retourne donc undefined
return undefined

// Étape 2 : ExĂ©cuter a(undefined)
a(undefined)
↓
// undefined est passé en paramÚtre à a
// À l'intĂ©rieur de a, on tente d'exĂ©cuter aa()
// C'est-Ă -dire undefined()
undefined() // ❌ Erreur : TypeError: aa is not a function

Pourquoi cela se produit-il ?​

1. Ordre d'exĂ©cution des fonctions (de l'intĂ©rieur vers l'extĂ©rieur)​

// Exemple
console.log(add(multiply(2, 3)));
↑ ↑
| └─ 2. D'abord exĂ©cuter multiply(2, 3) → 6
└────── 3. Ensuite exĂ©cuter add(6)

// MĂȘme concept
a(b(c))
↑ ↑
| └─ 1. D'abord exĂ©cuter b(c)
└─── 2. Ensuite exĂ©cuter a(rĂ©sultat de b(c))

2. Les fonctions sans return retournent undefined​

function b(bb) {
bb(); // Exécuté, mais pas de return
} // return undefined implicite

// Équivalent à
function b(bb) {
bb();
return undefined; // Ajouté automatiquement par JavaScript
}

3. Essayer d'appeler quelque chose qui n'est pas une fonction provoque une erreur​

const notAFunction = undefined;
notAFunction(); // TypeError: notAFunction is not a function

// Autres cas qui provoquent des erreurs
null(); // TypeError
123(); // TypeError
'string'(); // TypeError

Comment corriger ?​

MĂ©thode 1 : Faire en sorte que la fonction b retourne une fonction​

function a(aa) {
aa();
}

function b(bb) {
bb();
return function () {
console.log('b executed');
};
}

function c() {
console.log('hello');
}

a(b(c));
// Sortie :
// hello
// b executed

MĂ©thode 2 : Passer la fonction directement, sans l'exĂ©cuter d'abord​

function a(aa) {
aa();
}

function b(bb) {
return function () {
bb();
};
}

function c() {
console.log('hello');
}

a(b(c)); // N'affiche que 'hello'

// Ou écrire de cette façon
a(() => b(c)); // Affiche 'hello'

MĂ©thode 3 : Modifier la logique d'exĂ©cution​

function a(aa) {
aa();
}

function b(bb) {
bb();
}

function c() {
console.log('hello');
}

// Exécuter séparément
b(c); // Affiche 'hello'
a(() => console.log('a executed')); // Affiche 'a executed'

Questions connexes​

Question 1 : Que se passe-t-il si on modifie comme ceci ?​

function a(aa) {
return aa();
}

function b(bb) {
return bb();
}

function c() {
console.log('hello');
return 'world';
}

console.log(a(b(c)));
Cliquez pour voir la réponse
hello
TypeError: aa is not a function

Analyse :

  1. b(c) → ExĂ©cute c(), affiche 'hello', retourne 'world'
  2. a('world') → ExĂ©cute 'world'()... attendez, ça provoque aussi une erreur !

Bonne réponse :

hello
TypeError: aa is not a function

Parce que b(c) retourne 'world' (une chaĂźne), a('world') tente d'exĂ©cuter 'world'(), une chaĂźne n'est pas une fonction, d'oĂč l'erreur.

Question 2 : Et si tous ont un return ?​

function a(aa) {
return aa;
}

function b(bb) {
return bb;
}

function c() {
return 'hello';
}

const result = a(b(c));
console.log(result);
console.log(result());
Cliquez pour voir la réponse
[Function: c]
hello

Analyse :

  1. b(c) → Retourne la fonction c elle-mĂȘme (sans l'exĂ©cuter)
  2. a(c) → Retourne la fonction c elle-mĂȘme
  3. result est la fonction c
  4. result() → ExĂ©cute c(), retourne 'hello'

Points clĂ©s Ă  retenir​

// Priorité des appels de fonctions
a(b(c))
↓
// 1. D'abord exécuter la plus interne
b(c) // Si b n'a pas de return, c'est undefined
↓
// 2. Ensuite exécuter l'externe
a(undefined) // Tenter d'exécuter undefined() provoque une erreur

// Solutions
// ✅ 1. S'assurer que les fonctions intermĂ©diaires retournent une fonction
// ✅ 2. Ou utiliser des arrow functions pour encapsuler
a(() => b(c))

Reference​