Comment éviter les fuites mémoire dans les fermetures JavaScript
Les fermetures JavaScript maintiennent les objets en vie en mémoire tant qu'il existe une référence à la fonction de fermeture. Cela provoque des fuites lorsque un objet n'est plus nécessaire, mais que le ramasse-miettes ne peut pas le libérer car il est capturé dans la fermeture.
Considérons un cas typique avec un gros objet :
let bigData = {
id: 42,
data: new Array(1000000).fill('Quelques données')
}
function handler(data) {
return function() {
console.log(data.id);
debugger;
}
}
const runHandler = handler(bigData);
bigData = null;
runHandler();
La fonction interne n'utilise que data.id, mais la fermeture capture l'objet bigData entier. Dans DevTools au point d'arrêt, vous verrez que l'objet persiste encore en mémoire.
Problèmes avec eval() dans les fermetures
La fonction eval() aggrave les fuites car le moteur ajoute toutes les variables externes à la fermeture, qu'elles soient utilisées ou non :
let hello = 'Bonjour';
let nomUtilisateur = 'Jean';
setTimeout(function () {
debugger;
return eval('console.log("Bonjour de Jean !")');
}, 1000);
Dans DevTools, la fermeture inclut hello et nomUtilisateur, même si eval() ne les référence pas. Cela vient de l'incertitude du code à l'intérieur de eval(). Évitez eval() en production.
Méthodes pour prévenir les fuites
Forcer la réinitialisation des données
Réinitialisez les données capturées après usage :
function runCalc(buttonId) {
let bigData = new Array(1000000).fill('Quelque chose');
const button = document.getElementById(buttonId);
button.addEventListener('click', function onClick() {
console.log('Éléments traités :', bigData.length);
bigData = null;
});
}
Assigner null libère la référence dans la fermeture, permettant au ramasse-miettes de supprimer l'objet.
Supprimer correctement les gestionnaires d'événements
Les gestionnaires d'événements sont une source courante de fuites lors de la suppression d'éléments DOM :
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message);
function handler() {
console.log(message, bigData.length);
btn.remove();
btn.removeEventListener('click', handler);
}
btn.addEventListener('click', handler);
}
Une fonction nommée handler peut être supprimée avec l'élément. Les fonctions fléchées anonymes ne le peuvent pas de cette façon.
WeakMap pour les références faibles
WeakMap résout le problème de capture d'objets volumineux en utilisant des références faibles vers les clés objets.
Les clés doivent être des objets
const weak = new WeakMap();
const key = {};
weak.set(key, 'Jean est passé par là !');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined
Les clés doivent être des objets ou des non-primitifs. Accès via get(key) par référence.
Références faibles et ramasse-miettes
Les références dans WeakMap n'empêchent pas le ramassage des déchets :
const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Jean');
userKey1 = null;
Après avoir mis userKey1 à null, l'objet et sa valeur sont ramassés par le garbage collector.
Pas d'itérateurs
Pas de size, forEach, keys(). Seulement les méthodes de base :
set(key, value)— définirget(key)— obtenirhas(key)— vérifierdelete(key)— supprimer
Pas d'itérateurs pour éviter l'imprévisibilité pendant le garbage collection.
Utiliser WeakMap dans les fermetures
Une fermeture avec WeakMap ne retient pas les objets fortement :
function createProcessor() {
const weakMap = new WeakMap();
return function process(obj) {
weakMap.set(obj, obj.value);
debugger;
return weakMap.get(obj);
}
}
const processor = createProcessor();
let bigData = {
value: 21,
hugeArray: new Array(1000000).fill('*')
};
console.log(processor(bigData));
bigData = null;
let bigData2 = {
value: 42,
hugeArray: new Array(1000000).fill('1')
};
console.log(processor(bigData2));
Dans DevTools après garbage collection, seul bigData2 reste. WeakMap permet un nettoyage mémoire automatique.
Pour vérifier :
- Mettez un point d'arrêt dans
process(). - Avancez pas à pas (F10).
- Après
bigData = null, passez à l'onglet Memory. - Déclenchez le garbage collection avec le bouton.
- Continuez — seules les clés actuelles restent.
Points clés à retenir
- Les fermetures capturent les objets entiers, même si une seule propriété est utilisée.
eval()ajoute toutes les variables externes à la fermeture.- Les gestionnaires d'événements nommés peuvent être supprimés via
removeEventListener. WeakMaputilise des références faibles vers les clés objets.- Pas d'itérateurs dans
WeakMappour un garbage collection prévisible.
— Editorial Team
Aucun commentaire pour le moment.