Qu’est-ce qu’une closure ?
Une closure est une fonction qui a accès aux variables de sa portée lexicale externe, même après que la fonction externe ait terminé son exécution.
Définition simple : Fonction + Environnement lexical sauvegardé = Closure
Exemple :
function outer() {
const message = 'Hello';
return function inner() {
console.log(message); // inner capture message
};
}
const myFunction = outer();
myFunction(); // 'Hello' - message existe encore !Quel est le mnémonique pour les closures ?
C.L.O.S.E
Comment résoudre le problème classique des closures dans les boucles avec var ?
Problème :
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Affiche : 3, 3, 3 ❌Solutions :
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Affiche : 0, 1, 2 ✓for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}function createLogger(value) {
return function() {
console.log(value);
};
}
for (var i = 0; i < 3; i++) {
setTimeout(createLogger(i), 100);
}Citez 5 cas d’usage pratiques des closures
Créez un compteur avec données privées utilisant les closures
function createCounter() {
let count = 0; // Variable PRIVÉE
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2
// ❌ Impossible d'accéder directement
console.log(counter.count); // undefinedQuel est le piège avec les closures et la mémoire ?
Les closures gardent des références aux variables externes en mémoire, ce qui peut empêcher le garbage collection si mal utilisé.
Problème :
function createHugeArray() {
const hugeArray = new Array(1000000).fill('data');
return function() {
console.log('Fonction créée');
};
}
const fn = createHugeArray();
// hugeArray reste en mémoire tant que fn existe !Solution :
// Ne pas capturer si pas nécessaire // Ou libérer explicitement : let fn = createHugeArray(); fn = null; // Permet le garbage collection
Les closures gardent-elles une copie ou une référence des variables ?
Une RÉFÉRENCE, pas une copie.
Si la variable change, la closure voit le changement.
Exemple :
function createFunctions() {
const functions = [];
for (var i = 0; i < 3; i++) {
functions.push(function() {
return i; // Référence à i
});
}
return functions;
}
const fns = createFunctions();
fns[0](); // 3 (pas 0 !)
fns[1](); // 3 (pas 1 !)
fns[2](); // 3Toutes les fonctions référencent la MÊME variable i.
Créez une factory function pour multiplier par un facteur
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20Chaque fonction retournée a sa propre closure avec son propre ‘factor’.
Implémentez une fonction ‘once’ utilisant les closures
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
// Usage
function initialize() {
console.log('Initializing...');
return 'Initialized';
}
const initOnce = once(initialize);
console.log(initOnce()); // 'Initializing...' → 'Initialized'
console.log(initOnce()); // 'Initialized' (pas de log)
console.log(initOnce()); // 'Initialized' (pas de log)Créez une fonction de memoization avec closures
function memoize(fn) {
const cache = {}; // Capturé par closure
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log('Depuis le cache');
return cache[key];
}
console.log('Calcul...');
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Usage
function slowSquare(n) {
return n * n;
}
const memoizedSquare = memoize(slowSquare);
console.log(memoizedSquare(5)); // 'Calcul...' → 25
console.log(memoizedSquare(5)); // 'Depuis le cache' → 25
console.log(memoizedSquare(6)); // 'Calcul...' → 36Quelle est la différence entre scope et closure ?
Scope (Portée) :
- Règles qui déterminent OÙ les variables sont accessibles
- Déterminé par la structure du code
Closure (Fermeture) :
- Fonction + son environnement lexical sauvegardé
- Permet d’accéder aux variables même après que la fonction externe ait terminé
Relation :
Les closures utilisent le lexical scope pour capturer et maintenir l’accès aux variables.
Pourquoi les closures existent-elles en JavaScript ?
JavaScript est garbage-collecté : les variables inutilisées sont automatiquement supprimées.
Sans closure :
function create() {
const data = 'Important';
}
create();
// data est détruit par le garbage collectorAvec closure :
function create() {
const data = 'Important';
return function() {
console.log(data);
};
}
const fn = create();
// data N'EST PAS détruit car fn y a encore accès !
fn(); // 'Important'Le garbage collector garde ‘data’ en mémoire tant que fn existe.
Qu’est-ce que ‘this’ en JavaScript ?
‘this’ est un mot-clé qui référence un objet, mais quel objet dépend de comment la fonction est appelée.
Règle d’or : ‘this’ est déterminé à l’exécution (runtime), pas à la définition.
Exemple :
function greet() {
console.log(this.name);
}
const person = { name: 'Alice' };
greet(); // undefined (this = global)
greet.call(person); // 'Alice' (this = person)Quelles sont les 4 règles de binding pour ‘this’ (par ordre de priorité) ?
1. NEW binding (Priorité la plus haute)
- Appelée avec ‘new’, this = nouvel objet
2. Explicit binding
- call/apply/bind, this = objet passé
3. Implicit binding
- objet.méthode(), this = objet
4. Default binding (Priorité la plus basse)
- this = window/global (ou undefined en strict mode)
Ordre : new > explicit > implicit > default
Quel est le mnémonique pour ‘this’ ?
B.I.N.D
Que se passe-t-il dans ce code ?
const obj = {
name: 'Alice',
greet() { console.log(this.name); }
};
const greet = obj.greet;
greet();Résultat : ‘undefined’ (strict mode) ou ‘[nom de window.name]’ (non-strict)
Explication :
La méthode perd son contexte quand elle est assignée à une variable. ‘this’ devient l’objet global/undefined car il n’y a plus de binding implicite.
Solutions :
// 1. Arrow function const greet = () => obj.greet(); // 2. bind const greet = obj.greet.bind(obj); // 3. Appeler directement obj.greet();
Les arrow functions ont-elles leur propre ‘this’ ?
Non. Les arrow functions n’ont PAS leur propre ‘this’.
Elles héritent du ‘this’ de leur contexte lexical (où elles sont définies).
Conséquences :
- Elles ignorent call/apply/bind
- Elles ne peuvent pas être utilisées comme constructeurs
Exemple :
const obj = {
name: 'Alice',
regular: function() {
console.log(this.name); // 'Alice'
},
arrow: () => {
console.log(this.name); // undefined (this global)
}
};
obj.regular(); // 'Alice'
obj.arrow(); // undefinedComment fixer le problème de ‘this’ dans setTimeout ?
Problème :
const person = {
name: 'Alice',
greet() {
setTimeout(function() {
console.log(this.name); // undefined ❌
}, 1000);
}
};Solutions :
1. Arrow function (Recommandé) :
setTimeout(() => {
console.log(this.name); // 'Alice' ✓
}, 1000);2. bind :
setTimeout(function() {
console.log(this.name);
}.bind(this), 1000);3. Variable ‘that’ :
const that = this;
setTimeout(function() {
console.log(that.name); // 'Alice' ✓
}, 1000);Différence entre call, apply et bind ?
call(thisArg, arg1, arg2, …) :
- Invoque IMMÉDIATEMENT
- Arguments séparés par virgules
apply(thisArg, [args]) :
- Invoque IMMÉDIATEMENT
- Arguments en tableau
bind(thisArg, arg1, …) :
- N’invoque PAS
- Retourne une NOUVELLE fonction avec ‘this’ fixé
Mnémonique :
- Call : Comma-separated, Calls now
- Apply : Array, Also calls now
- Bind : Binds, But doesn’t call
Que vaut ‘this’ avec le new keyword ?
Avec ‘new’, JavaScript crée un nouveau objet et ‘this’ pointe vers cet objet.
Exemple :
function User(name) {
this.name = name;
}
const user = new User('Alice');
console.log(user.name); // 'Alice'Ce que fait ‘new’ en coulisses :
function User(name) {
// 1. const this = {};
// 2. this.\_\_proto\_\_ = User.prototype;
this.name = name;
// 3. return this;
}Que vaut ‘this’ dans une arrow function au niveau global ?
Le ‘this’ du contexte parent (généralement ‘window’ en navigateur, ou l’objet global en Node.js).
Exemple :
const greet = () => {
console.log(this);
};
greet(); // window (ou global en Node.js)Important :
const obj = {
value: 42,
getValue: () => this.value
};
obj.getValue(); // undefined
// L'arrow function hérite du this global, pas de obj !Fixez ce code :
const person = {
name: 'Bob',
friends: ['Alice', 'Charlie'],
showFriends() {
this.friends.forEach(function(friend) {
console.log(this.name + ' knows ' + friend);
});
}
};Solutions :
1. Arrow function (Recommandé) :
showFriends() {
this.friends.forEach(friend => {
console.log(this.name + ' knows ' + friend);
});
}2. bind :
showFriends() {
this.friends.forEach(function(friend) {
console.log(this.name + ' knows ' + friend);
}.bind(this));
}3. thisArg (forEach accepte un 2e paramètre) :
showFriends() {
this.friends.forEach(function(friend) {
console.log(this.name + ' knows ' + friend);
}, this); // ← thisArg
}Peut-on changer le ‘this’ d’une arrow function avec bind ?
Non. Le ‘this’ d’une arrow function est fixé à sa création (lexical binding) et ne peut PAS être changé.
Exemple :
const greet = () => {
console.log(this.name);
};
const person = { name: 'Alice' };
greet.call(person); // undefined (ignoré!)
greet.apply(person); // undefined (ignoré!)
const bound = greet.bind(person);
bound(); // undefined (ignoré!)Les arrow functions ignorent complètement call, apply et bind pour ‘this’.
Comment créer une calculatrice chaînée avec ‘this’ ?
const calculator = {
value: 0,
add(n) {
this.value += n;
return this; // Retourne l'objet pour chaînage
},
subtract(n) {
this.value -= n;
return this;
},
multiply(n) {
this.value *= n;
return this;
},
divide(n) {
this.value /= n;
return this;
},
result() {
return this.value;
}
};
const result = calculator
.add(5)
.multiply(3)
.add(2)
.result();
console.log(result); // 17