Kodokon kodokon.com

Mémoire et performance : GC, WeakMap/WeakRef, debounce/throttle

Comprenez le fonctionnement du garbage collector, exploitez les références faibles pour éviter les fuites et lissez les traitements coûteux avec debounce et throttle.

11 min · 3 questions

Ouvrir cette leçon dans Kodokon

Le garbage collector de V8 repose sur l'accessibilité : un objet est collectable dès qu'aucune chaîne de références ne le relie aux racines (pile, portées actives, globales). Le GC est générationnel : les objets jeunes vivent dans la *nursery*, collectée souvent et vite (scavenge) ; les survivants sont promus vers l'ancienne génération, traitée par un mark-and-sweep incrémental. Les fuites classiques ne viennent pas du GC mais de vos références oubliées : caches en Map, listeners jamais détachés, closures capturant de gros objets.

JAVASCRIPT
const strongCache = new Map();
const weakCache = new WeakMap();

let session = { user: "ada" };
strongCache.set(session, "data");
weakCache.set(session, "data");

session = null;
// Map : la cle retient l'objet -> fuite memoire
// WeakMap : l'entree devient collectable
Même code, deux destins mémoire opposés.

Une WeakMap ne retient pas ses clés : quand la clé devient inaccessible par ailleurs, l'entrée entière disparaît. Conséquences de conception : les clés doivent être des objets (ou des symboles non enregistrés), et la structure n'est ni itérable ni mesurable - exposer sa taille révélerait le comportement du GC, qui est non déterministe. WeakRef va plus loin : elle vous donne une référence faible directe, à déréférencer via deref().

JAVASCRIPT
let config = { theme: "dark" };
const ref = new WeakRef(config);

function readTheme() {
  const target = ref.deref();
  return target ? target.theme : "default";
}

console.log(readTheme()); // "dark"
config = null;
// Apres un passage du GC, deref() peut
// renvoyer undefined : prevoyez un repli.
deref() peut renvoyer l'objet... ou undefined.

Côté performance perçue, les événements à haute fréquence (scroll, resize, input, mousemove) peuvent déclencher des centaines d'appels par seconde. Deux stratégies complémentaires : debounce n'exécute la fonction qu'après une période de calme (idéal pour une recherche pendant la saisie), tandis que throttle garantit au plus une exécution par intervalle (idéal pour suivre un défilement).

JAVASCRIPT
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(
      () => fn.apply(this, args),
      delay
    );
  };
}
Debounce : seul le dernier appel d'une rafale compte.
JAVASCRIPT
function throttle(fn, interval) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}
Throttle : au plus une exécution par intervalle.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Pourquoi les clés d'une WeakMap doivent-elles être des objets et non des primitives comme des chaînes ?
    • Pour des raisons de performance de hachage
    • Parce que seule une référence vers un objet peut devenir inaccessible et déclencher la suppression de l'entrée
    • C'est une limitation historique levée depuis ES2021
    • Parce que les primitives ne peuvent pas servir de clés en JavaScript
  2. Quelle est la différence fondamentale entre debounce et throttle ?
    • Debounce exécute après une période de calme, throttle garantit au plus une exécution par intervalle
    • Debounce est asynchrone, throttle est synchrone
    • Throttle annule les appels précédents, debounce les met en file d'attente
    • Ce sont deux noms pour la même technique
  3. Que peut renvoyer ref.deref() sur une WeakRef ?
    • Toujours l'objet d'origine, tant que la WeakRef existe
    • L'objet s'il est encore vivant, ou undefined s'il a été collecté
    • Une copie profonde de l'objet
    • null si l'objet a été collecté