Comme nous le savons du chapitre Ramasse-miettes (garbage collection), le moteur JavaScript stocke une valeur en mĂ©moire pendant quâelle est accessible et peut potentiellement ĂȘtre utilisĂ©e.
Par exemple :
let john = { name: "John" };
// l'objet est accessible, john en est la référence
// écraser la référence
john = null;
// l'objet sera supprimé de la mémoire
Habituellement, les propriĂ©tĂ©s dâun objet ou des Ă©lĂ©ments dâun tableau ou dâune autre structure de donnĂ©es sont considĂ©rĂ©es comme accessibles et conservĂ©es en mĂ©moire pendant que cette structure de donnĂ©es est en mĂ©moire.
Par exemple, si nous mettons un objet dans un tableau, alors que le tableau est vivant, lâobjet sera Ă©galement vivant, mĂȘme sâil nây a pas dâautres rĂ©fĂ©rences.
Comme ceci :
let john = { name: "John" };
let array = [ john ];
john = null; // écraser la référence
// l'objet précédemment référencé par john est stocké dans le tableau
// donc il ne sera pas nettoyé
// nous pouvons l'obtenir sous forme de array[0]
Semblable Ă cela, si nous utilisons un objet comme clĂ© dans un Map classique, alors que le Map existe, cet objet existe Ă©galement. Il occupe de la mĂ©moire et ne peut pas ĂȘtre nettoyĂ© (garbage collected).
Par example :
let john = { name: "John" };
let map = new Map();
map.set(john, "...");
john = null; // écraser la référence
// John est stocké à l'intérieur du map
// nous pouvons l'obtenir en utilisant map.keys()
WeakMap est fondamentalement diffĂ©rent Ă cet Ă©gard. Cela nâempĂȘche pas le garbage collection des objets clĂ©s.
Voyons ce que cela signifie sur des exemples.
WeakMap
La premiĂšre diffĂ©rences entre Map et WeakMap est que les clĂ©s doivent ĂȘtre des objets, pas des valeurs primitives :
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "ok"); // fonctionne bien (object key)
// ne peut pas utiliser une chaßne de caractÚres comme clé
weakMap.set("test", "Whoops"); // Erreur, parce que "test" n'est pas un objet
Maintenant, si nous utilisons un objet comme clĂ©, et quâil nây a pas dâautres rĂ©fĂ©rences Ă cet objet â il sera automatiquement supprimĂ© de la mĂ©moire (et du map).
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // on écrase la référence
// John est supprimé de la mémoire !
Comparez-le avec lâexemple du Map ci-dessus. Maintenant, si john nâexiste que comme clĂ© de WeakMap â il sera automatiquement supprimĂ© du map (et de la mĂ©moire).
WeakMap ne prend pas en charge lâitĂ©ration et les mĂ©thodes keys(), values(), entries(), il nây a donc aucun moyen dâen obtenir toutes les clĂ©s ou valeurs.
WeakMap nâa que les mĂ©thodes suivantes :
Pourquoi une telle limitation ? Câest pour des raisons techniques. Si un objet a perdu toutes les autres rĂ©fĂ©rences (comme john dans le code ci-dessus), il doit ĂȘtre automatiquement nettoyĂ©. Mais techniquement, ce nâest pas exactement spĂ©cifiĂ© quand le nettoyage a lieu.
Le moteur JavaScript dĂ©cide de cela. Il peut choisir dâeffectuer le nettoyage de la mĂ©moire immĂ©diatement ou dâattendre et de faire le nettoyage plus tard lorsque dâautres suppressions se produisent. Donc, techniquement, le nombre dâĂ©lĂ©ments actuel dâun WeakMap nâest pas connu. Le moteur peut lâavoir nettoyĂ© ou non, ou lâa fait partiellement. Pour cette raison, les mĂ©thodes qui accĂšdent Ă toutes les clĂ©s/valeurs ne sont pas prises en charge.
Maintenant, oĂč avons-nous besoin dâune telle structure de donnĂ©es ?
Cas dâutilisation : donnĂ©es supplĂ©mentaires
Le principal domaine dâapplication de WeakMap est un stockage de donnĂ©es supplĂ©mentaire.
Si nous travaillons avec un objet qui âappartientâ Ă un autre code, peut-ĂȘtre mĂȘme une bibliothĂšque tierce, et que nous souhaitons stocker certaines donnĂ©es qui lui sont associĂ©es, cela ne devrait exister que lorsque lâobjet est vivant â alors WeakMap est exactement ce quâil nous faut.
Nous plaçons les donnĂ©es dans un WeakMap, en utilisant lâobjet comme clĂ©, et lorsque lâobjet est nettoyĂ©, ces donnĂ©es disparaissent automatiquement Ă©galement.
weakMap.set(john, "secret documents");
// si John meurt, les documents secrets seront détruits automatiquement
Regardons un exemple.
Par exemple, nous avons un code qui conserve un nombre de visites pour les utilisateurs. Les informations sont stockĂ©es dans un map : un objet utilisateur est la clĂ© et le nombre de visites est la valeur. Lorsquâun utilisateur quitte (son objet est nettoyĂ©), nous ne voulons plus stocker son nombre de visites.
Voici un exemple dâune fonction de comptage avec Map :
// đ visitsCount.js
let visitsCountMap = new Map(); // map: user => visits count
// augmentons le nombre de visites
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
Et voici une autre partie du code, peut-ĂȘtre un autre fichier qui lâutilise :
// đ main.js
let john = { name: "John" };
countUser(john); // compter ses visites
// plus tard, John nous quitte
john = null;
Maintenant, lâobjet john doit ĂȘtre nettoyĂ©, mais cependant, il reste en mĂ©moire, parce que câest une clĂ© dans visitesCountMap.
Nous devons nettoyer visitesCountMap lorsque nous supprimons des utilisateurs, sinon il augmentera indéfiniment en mémoire. Un tel nettoyage peut devenir une tùche fastidieuse dans des architectures complexes.
Nous pouvons éviter cela en utilisant WeakMap :
// đ visitsCount.js
let visitsCountMap = new WeakMap(); // weakmap: user => visits count
// augmentons le nombre de visites
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
Maintenant, nous nâavons plus Ă nettoyer visitesCountMap. AprĂšs que lâobjet john devienne inaccessible autrement que en tant que clĂ© de WeakMap, il est supprimĂ© de la mĂ©moire, en mĂȘme temps que les informations de cette clĂ© dans WeakMap.
Cas dâutilisation : mise en cache
Un autre exemple courant est la mise en cache. Nous pouvons stocker (âcacheâ) les rĂ©sultats dâune fonction, afin que les futurs appels sur le mĂȘme objet puissent le rĂ©utiliser.
Pour y parvenir, nous pouvons utiliser Map (scénario non optimal) :
// đ cache.js
let cache = new Map();
// calculons et mémorisons le résultat
function process(obj) {
if (!cache.has(obj)) {
let result = /* calculs du résultat pour */ obj;
cache.set(obj, result);
return result;
}
return cache.get(obj);
}
// Maintenant, utilisons process() dans un autre fichier :
// đ main.js
let obj = {/* disons que nous avons un objet */};
let result1 = process(obj); // calculé
// ⊠plus tard, d'un autre endroit du code âŠ
let result2 = process(obj); // résultat mémorisé provenant du cache
// ⊠plus tard, lorsque l'objet n'est plus nécessaire :
obj = null;
alert(cache.size); // 1 (Ouch ! L'objet est toujours dans le cache, prenant de la mémoire !)
Pour plusieurs appels de process(obj) avec le mĂȘme objet, il ne calcule le rĂ©sultat que la premiĂšre fois, puis le prend simplement dans cache. LâinconvĂ©nient est que nous devons nettoyer le cache lorsque lâobjet nâest plus nĂ©cessaire.
Si nous remplaçons Map par WeakMap, alors ce problĂšme disparaĂźt : le rĂ©sultat mis en cache sera automatiquement supprimĂ© de la mĂ©moire une fois que lâobjet sera nettoyĂ©.
// đ cache.js
let cache = new WeakMap();
// calculons et mémorisons le résultat
function process(obj) {
if (!cache.has(obj)) {
let result = /* calculer le résultat pour */ obj;
cache.set(obj, result);
return result;
}
return cache.get(obj);
}
// đ main.js
let obj = {/* un objet */};
let result1 = process(obj);
let result2 = process(obj);
// ⊠plus tard, lorsque l'objet n'est plus nécessaire :
obj = null;
// Impossible d'obtenir cache.size, car c'est un WeakMap,
// mais c'est 0 ou bientĂŽt 0
// Lorsque obj est nettoyé, les données mises en cache seront également supprimées
WeakSet
WeakSet se comporte de la mĂȘme maniĂšre :
- Il est analogue Ă
Set, mais nous pouvons seulement ajouter des objets ĂWeakSet(pas de primitives). - Un objet existe dans le set tant quâil est accessible ailleurs.
- Comme
Set, il prend en chargeadd,hasetdelete, mais passize,keys()et aucune itération.
Ătant âweakâ (faible), il sert Ă©galement de stockage supplĂ©mentaire. Mais pas pour des donnĂ©es arbitraires, mais plutĂŽt pour des faits âoui/nonâ. Une appartenance Ă WeakSet peut signifier quelque chose Ă propos de lâobjet.
Par exemple, nous pouvons ajouter des utilisateurs à WeakSet pour garder une trace de ceux qui ont visité notre site :
let visitedSet = new WeakSet();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
visitedSet.add(john); // John nous a rendu visite
visitedSet.add(pete); // Ensuite Pete
visitedSet.add(john); // John encore
// visitedSet a 2 utilisateurs maintenant
// vérifions si John est venu
alert(visitedSet.has(john)); // true
// vérifions si Mary est venue
alert(visitedSet.has(mary)); // false
john = null;
// visitedSet sera nettoyé automatiquement
La limitation la plus notable de WeakMap et WeakSet est lâabsence dâitĂ©rations et lâimpossibilitĂ© dâobtenir tout le contenu actuel. Cela peut sembler gĂȘnant, mais nâempĂȘche pas WeakMap/WeakSet de faire leur travail principal â ĂȘtre un stockage âsupplĂ©mentaireâ de donnĂ©es pour les objets qui sont stockĂ©s/gĂ©rĂ©s Ă un autre endroit.
Résumé
WeakMap est une sorte de collection Map qui nâautorise que des objets comme clĂ©s et les supprime avec la valeur associĂ©e une fois quâils deviennent inaccessibles par dâautres moyens.
WeakSet est une sorte de collection Set qui ne stocke que des objets et les supprime une fois quâils deviennent inaccessibles par dâautres moyens.
Leurs principaux avantages sont quâils ont une faible rĂ©fĂ©rence aux objets, de sorte quâils peuvent facilement ĂȘtre supprimĂ©s par le garbage collector.
Cela se fait au prix de ne pas avoir de support pour clear, size, keys, valuesâŠ
WeakMap et WeakSet sont utilisĂ©es comme structures de donnĂ©es âsecondairesâ en plus du stockage dâobjets âprincipalâ. Une fois que lâobjet est retirĂ© du stockage principal, sâil nâest trouvĂ© que comme clĂ© de WeakMap ou dans un WeakSet, il sera nettoyĂ© automatiquement.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâŠ)