🌿 Comprendre JS
Les données
Le garbage collector

Lorsqu'on a appris le développement logiciel avec des langages comme C ou C++, le passage à Javascript se fait en douceur. Mais parmi les points déroutants, il y a la gestion implicite de la mémoire dans JavaScript. Comme beaucoup de langage, le développeur n'a pas à s'en préoccuper grâce au "garbage collector". Il s'agit d'un composant essentiel du moteur JavaScript qui vous permet de gérer la mémoire de manière automatisée.

Dans cet article, nous allons plonger dans le monde du garbage collector en JavaScript, comprendre comment il fonctionne et comment vous pouvez en tirer profit pour écrire un code plus performant.

Qu'est-ce que le Garbage Collector ?

Le garbage collector est une fonctionnalité du moteur JavaScript qui gère la mémoire en suivant les références des objets créés dans votre programme. En JavaScript, la gestion de la mémoire est gérée automatiquement, ce qui signifie que vous n'avez pas besoin de vous soucier de la libération de la mémoire manuellement, comme c'est le cas dans d'autres langages tels que C++.

Le principe de base du garbage collector est simple : il identifie les objets qui ne sont plus accessibles depuis votre programme et les supprime de la mémoire. Cela permet d'éviter les fuites de mémoire, qui se produisent lorsque des objets inutiles continuent d'occuper de l'espace mémoire, ce qui peut entraîner des problèmes de performance et des crashs de l'application.

Comment fonctionne le Garbage Collector en JavaScript ?

En JavaScript, les objets sont créés et stockés en mémoire, et des références sont utilisées pour accéder à ces objets. Lorsqu'une référence pointe vers un objet, cet objet est considéré comme accessible. Lorsqu'il n'y a plus de références pointant vers un objet, cet objet devient éligible à la collecte par le garbage collector.

Le garbage collector utilise généralement l'algorithme de "marquage et balayage" (mark and sweep) pour déterminer quels objets sont accessibles et lesquels ne le sont plus. Voici comment cela fonctionne :

  1. Marquage : Le garbage collector commence par marquer tous les objets accessibles à partir d'une "racine". Les racines sont généralement les variables globales et les objets du DOM (Document Object Model). Tous les objets accessibles depuis les racines sont marqués comme tels.

  2. Balayage : Après le marquage, le garbage collector parcourt toutes les structures de données pour trouver les objets qui n'ont pas été marqués. Ces objets sont considérés comme inaccessibles et sont supprimés de la mémoire.

  3. Compactage (parfois) : Dans certaines implémentations, le garbage collector peut également compacter la mémoire en déplaçant les objets restants vers une zone de mémoire continue, ce qui peut améliorer l'utilisation de la mémoire.

Il faut avouer que ce système est très reposant ! On code sans se soucier de la gestion de la mémoire, et on se repose sur le garbage collector pour faire le ménage.

Mais il y a quand même quelques précautions à prendre pour lui faciliter la vie.

Comment tirer profit du Garbage Collector dans votre code ?

Maintenant que vous avez une idée générale de la manière dont le garbage collector fonctionne, voyons comment vous pouvez en tirer profit pour écrire un code JavaScript plus performant.

Globalement, il y a 2 façons de libérer une référence pour la rendre éligible au balayage du garbage collector :

  • explicite : positionner la référence à la valeur null
  • implicite : rendre la référence hors de portée, donc inatteignable par le reste du code

Voici 3 erreurs fréquentes qui peuvent conduire à des fuites mémoires car le garbage collector ne parvient pas à libérer des objets.

1. Gérez vos références

L'une des erreurs les plus courantes qui entraînent des fuites de mémoire est la rétention involontaire de références vers des objets. Assurez-vous de libérer explicitement les références vers les objets que vous n'utilisez plus. Cela permettra au garbage collector de les collecter plus rapidement.

let myObject = {
  // ... propriétés de l'objet ...
};

// Utilisez myObject

myObject = null; // explicite
/* ou implicite */
myObject = {
    // .... myObject référence un autre objet -> libération de l'ancien
}

Tant qu'il existe quelque part une référence vers un objet, il n'est pas libéré.

2. N'oubliez pas le retrait des handlers d'événements

Lorsque vous attachez des gestionnaires d'événements à des éléments DOM, assurez-vous de les détacher lorsque vous n'en avez plus besoin. Les gestionnaires d'événements peuvent retenir des références vers des objets DOM, ce qui peut entraîner des fuites de mémoire.

const button = document.getElementById('myButton');

function onClick() {
  // Gestionnaire d'événements
}

button.addEventListener('click', onClick);

// Pour détacher le gestionnaire d'événements
button.removeEventListener('click', onClick);

3. Prudence avec les closures

Les closures en JavaScript peuvent retenir des références vers des variables extérieures même après leur exécution.

function outerFunction() {
  let outerVar = "Je suis une variable externe";

  function innerFunction() {
    console.log(outerVar); // innerFunction capture outerVar
  }

  return innerFunction;
}

const closure = outerFunction();
closure(); // Affichera "Je suis une variable externe" car elle a capturé outerVar

Dans cet exemple précédent, innerFunction est une closure qui capture outerVar. On pourrait croire que outerVar est libérée car c'est une variable locale à la fonction outerFunction, donc libérée après l'exécution de celle-ci.

❌ Erreur !

Même après l'exécution de outerFunction, la closure innerFunction conserve une référence à outerVar ! Pour libérer outerVar, il faut libérer innerFunction.

Le profilage

Le garbage collector en JavaScript est un composant essentiel qui vous permet de gérer la mémoire de manière automatisée. En comprenant comment il fonctionne et en suivant quelques bonnes pratiques de programmation, vous pouvez écrire un code JavaScript plus performant et éviter les fuites de mémoire qui pourraient nuire à vos applications web.

Pour analyser les fuites mémoires et en déterminer l'origine, il existe des outils de profilage qui vous permettent d'analyser les performances de votre code.

Pour une application Node, utilisez le flag --inspect

node --inspect index.js

Debugger listening on ws://127.0.0.1:9229/7fc22153-836d-4ed2-8090-a84a842a199e
For help, see: <https://nodejs.org/en/docs/inspector>
Sample app listening on port 3000.

Ensuite, connectez un client pour exploiter les données de profilage envoyées par Node. Par exemple, avec Chrome à l'url chrome://inspect

Chrome dev tools

L'éditeur de code Visual Studio Code propose également des fonctionnalités de profilage intégrées pour JavaScript. Vous pouvez utiliser des extensions VS Code telles que "Debugger for Chrome" pour connecter VS Code aux Chrome DevTools et profiler votre code JavaScript.

Ces outils de profilage vous aideront à optimiser les performances de votre code JavaScript, à résoudre les problèmes de mémoire, et à améliorer l'expérience utilisateur de vos applications web et Node.js. En les utilisant de manière judicieuse, vous pouvez identifier les domaines de votre code qui nécessitent une optimisation et améliorer la réactivité et l'efficacité de vos applications.