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â¦)