Comme nous le savons dans le chapitre Garbage collection, le moteur JavaScript conserve une valeur en mémoire tant qu'elle est « accessible » et peut potentiellement être utilisée.
Par exemple:
laissez john = { nom : "John" } ; // l'objet est accessible, john en est la référence // écrase la référence jean = nul ; // l'objet sera supprimé de la mémoire
Habituellement, les propriétés d'un objet ou d'é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 plaçons un objet dans un tableau, alors tant que le tableau est vivant, l'objet le sera également, même s'il n'y a aucune autre référence à lui.
Comme ça:
laissez john = { nom : "John" } ; laissez tableau = [ john ]; jean = nul ; // écrase la référence // l'objet précédemment référencé par John est stocké dans le tableau // donc il ne sera pas récupéré // nous pouvons l'obtenir sous forme de tableau[0]
De la même manière, si nous utilisons un objet comme clé dans un Map
normal, alors tant que le Map
existe, cet objet existe également. Il occupe de la mémoire et ne peut pas être récupéré.
Par exemple:
laissez john = { nom : "John" } ; laissez map = new Map(); map.set(john, "..."); jean = nul ; // écrase la référence // John est stocké dans la carte, // nous pouvons l'obtenir en utilisant map.keys()
WeakMap
est fondamentalement différent sur cet aspect. Cela n'empêche pas le garbage collection des objets clés.
Voyons ce que cela signifie sur des exemples.
La première différence entre Map
et WeakMap
est que les clés doivent être des objets et non des valeurs primitives :
laissez faibleMap = new WeakMap(); soit obj = {} ; faibleMap.set(obj, "ok"); // fonctionne bien (clé objet) // ne peut pas utiliser une chaîne comme clé faibleMap.set("test", "Oups"); // Erreur, car "test" n'est pas un objet
Désormais, si nous utilisons un objet comme clé et qu'il n'y a aucune autre référence à cet objet, il sera automatiquement supprimé de la mémoire (et de la carte).
laissez john = { nom : "John" } ; laissez faibleMap = new WeakMap(); faibleMap.set(john, "..."); jean = nul ; // écrase la référence // John est supprimé de la mémoire !
Comparez-le avec l’exemple Map
classique ci-dessus. Désormais, si john
n'existe qu'en tant que clé de WeakMap
, il sera automatiquement supprimé de la carte (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 :
weakMap.set(key, value)
weakMap.get(key)
weakMap.delete(key)
weakMap.has(key)
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), alors il doit être automatiquement récupéré. Mais techniquement, il n'est pas exactement précisé quand le nettoyage aura lieu .
Le moteur JavaScript en décide. Il peut choisir d'effectuer le nettoyage de la mémoire immédiatement ou d'attendre et d'effectuer le nettoyage plus tard lorsque d'autres suppressions se produisent. Donc, techniquement, le nombre actuel d’éléments d’un WeakMap
n’est pas connu. Le moteur l'a peut-être 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 ?
Le principal domaine d'application de WeakMap
est le stockage supplémentaire de données .
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, celles-ci ne devraient exister que tant que l'objet est vivant – alors WeakMap
est exactement ce qu'il faut faire. nécessaire.
Nous plaçons les données dans un WeakMap
, en utilisant l'objet comme clé, et lorsque l'objet est récupéré, ces données disparaissent également automatiquement.
faibleMap.set(john, "documents secrets"); // si John décède, les documents secrets seront automatiquement détruits
Regardons un exemple.
Par exemple, nous avons un code qui enregistre le nombre de visites des utilisateurs. Les informations sont stockées dans une carte : un objet utilisateur est la clé et le nombre de visites est la valeur. Lorsqu'un utilisateur quitte (son objet est récupéré), nous ne voulons plus stocker son nombre de visites.
Voici un exemple de fonction de comptage avec Map
:
// ? visitesCount.js laissez visitsCountMap = new Map(); // carte : utilisateur => nombre de visites // augmente le nombre de visites fonction countUser (utilisateur) { laissez compter = visitsCountMap.get(user) || 0 ; visitesCountMap.set (utilisateur, nombre + 1); }
Et voici une autre partie du code, peut-être un autre fichier l'utilisant :
// ? main.js laissez john = { nom : "John" } ; countUser(john); // compte ses visites // plus tard, John nous quitte jean = nul ;
Désormais, l'objet john
doit être récupéré, mais reste en mémoire, car il s'agit d'une clé dans visitsCountMap
.
Nous devons nettoyer visitsCountMap
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 l’éviter en passant plutôt à WeakMap
:
// ? visitesCount.js laissez visitsCountMap = new WeakMap(); // lowmap : utilisateur => nombre de visites // augmente le nombre de visites fonction countUser (utilisateur) { laissez compter = visitsCountMap.get(user) || 0 ; visitesCountMap.set (utilisateur, nombre + 1); }
Désormais, nous n'avons plus besoin de nettoyer visitsCountMap
. Une fois que l'objet john
devient inaccessible, par tous les moyens sauf en tant que clé de WeakMap
, il est supprimé de la mémoire, ainsi que les informations de cette clé de WeakMap
.
Un autre exemple courant est la mise en cache. Nous pouvons stocker (« mettre en 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 laissez cache = new Map(); // calcule et mémorise le résultat fonction processus (obj) { si (!cache.has(obj)) { let result = /* calculs du résultat pour */ obj; cache.set(obj, résultat); renvoyer le résultat ; } retourner cache.get(obj); } // Maintenant, nous utilisons process() dans un autre fichier : // ? main.js let obj = {/* disons que nous avons un objet */}; soit result1 = process(obj); // calculé // ...plus tard, depuis un autre endroit du code... soit result2 = process(obj); // résultat mémorisé extrait du cache // ...plus tard, lorsque l'objet n'est plus nécessaire : obj = nul ; alerte(cache.size); // 1 (Aïe ! L'objet est toujours en cache, prenant de la mémoire !)
Pour plusieurs appels de process(obj)
avec le même objet, il calcule uniquement le résultat la première fois, puis le extrait simplement du cache
. L’inconvénient est que nous devons vider 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 aura été récupéré.
// ? cache.js laissez cache = new WeakMap(); // calcule et mémorise le résultat fonction processus (obj) { si (!cache.has(obj)) { laissez result = /* calculer le résultat pour */ obj; cache.set(obj, résultat); renvoyer le résultat ; } retourner cache.get(obj); } // ? main.js let obj = {/* un objet */} ; soit result1 = process(obj); soit result2 = process(obj); // ...plus tard, lorsque l'objet n'est plus nécessaire : obj = nul ; // Impossible d'obtenir cache.size, car c'est une WeakMap, // mais c'est 0 ou bientôt 0 // Lorsque obj est récupéré, les données mises en cache seront également supprimées
WeakSet
se comporte de la même manière :
C'est analogue à Set
, mais nous ne pouvons ajouter que des objets à WeakSet
(pas des primitives).
Un objet existe dans l’ensemble alors qu’il est accessible depuis un autre endroit.
Comme Set
, il prend en charge add
, has
et delete
, mais pas size
, keys()
et aucune itération.
Etant « 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 sur l'objet.
Par exemple, nous pouvons ajouter des utilisateurs à WeakSet
pour garder une trace de ceux qui ont visité notre site :
laissez visitSet = new WeakSet(); laissez john = { nom : "John" } ; laissez pete = { nom : "Pete" } ; laissez mary = { nom : "Marie" } ; visitéSet.add(john); // John nous a rendu visite visitéSet.add(pete); // Alors Pete visitéSet.add(john); // Encore John // visitSet a maintenant 2 utilisateurs // vérifier si John est venu nous rendre visite ? alert(visitedSet.has(john)); // vrai // vérifier si Marie est venue ? alert(visitedSet.has(mary)); // FAUX jean = nul ; // visitSet sera nettoyé automatiquement
La limitation la plus notable de WeakMap
et WeakSet
est l'absence d'itérations et l'incapacité 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 des objets qui sont stockés/gérés ailleurs.
WeakMap
est une collection de type Map
qui autorise uniquement les 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 collection de type Set
qui stocke uniquement les 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, ils peuvent donc être facilement supprimés par le ramasse-miettes.
Cela se fait au prix de ne pas prendre en charge les valeurs clear
, size
, keys
, values
…
WeakMap
et WeakSet
sont utilisés comme structures de données « secondaires » en plus du stockage d'objets « primaires ». Une fois l'objet supprimé du stockage principal, s'il n'est trouvé que comme clé de WeakMap
ou dans un WeakSet
, il sera nettoyé automatiquement.
importance : 5
Il existe une série de messages :
laissez messages = [ {texte : "Bonjour", de : "John"}, {texte : "Comment ça va ?", de : "John"}, {texte : "A bientôt", de : "Alice"} ];
Votre code peut y accéder, mais les messages sont gérés par le code de quelqu'un d'autre. De nouveaux messages sont ajoutés, les anciens sont régulièrement supprimés par ce code, et vous ne connaissez pas les moments exacts où cela se produit.
Maintenant, quelle structure de données pourriez-vous utiliser pour stocker des informations indiquant si le message « a été lu » ? La structure doit être bien adaptée pour donner la réponse « a-t-il été lu ? pour l'objet de message donné.
PS Lorsqu'un message est supprimé de messages
, il devrait également disparaître de votre structure.
PPS Nous ne devons pas modifier les objets de message, leur ajouter nos propriétés. Comme ils sont gérés par le code de quelqu'un d'autre, cela peut entraîner de mauvaises conséquences.
Stockons les messages lus dans WeakSet
:
laissez messages = [ {texte : "Bonjour", de : "John"}, {texte : "Comment ça va ?", de : "John"}, {texte : "A bientôt", de : "Alice"} ]; laissez readMessages = new WeakSet(); // deux messages ont été lus readMessages.add(messages[0]); readMessages.add(messages[1]); // readMessages a 2 éléments // ...relisons le premier message ! readMessages.add(messages[0]); // readMessages a encore 2 éléments uniques // réponse : le message[0] a-t-il été lu ? alert("Lire le message 0 : " + readMessages.has(messages[0])); // vrai messages.shift(); // readMessages a maintenant 1 élément (techniquement, la mémoire peut être nettoyée plus tard)
Le WeakSet
permet de stocker un ensemble de messages et de vérifier facilement l'existence d'un message dans celui-ci.
Il se nettoie automatiquement. Le compromis est que nous ne pouvons pas parcourir dessus, nous ne pouvons pas en extraire directement « tous les messages lus ». Mais nous pouvons le faire en parcourant tous les messages et en filtrant ceux qui se trouvent dans l'ensemble.
Une autre solution différente pourrait consister à ajouter une propriété telle que message.isRead=true
à un message après sa lecture. Comme les objets messages sont gérés par un autre code, cela est généralement déconseillé, mais on peut utiliser une propriété symbolique pour éviter les conflits.
Comme ça:
// la propriété symbolique n'est connue que de notre code let isRead = Symbole("isRead"); messages[0][isRead] = vrai ;
Désormais, le code tiers ne verra probablement pas notre propriété supplémentaire.
Bien que les symboles permettent de réduire la probabilité de problèmes, l'utilisation de WeakSet
est meilleure du point de vue architectural.
importance : 5
Il existe un tableau de messages comme dans la tâche précédente. La situation est similaire.
laissez messages = [ {texte : "Bonjour", de : "John"}, {texte : "Comment ça va ?", de : "John"}, {texte : "A bientôt", de : "Alice"} ];
La question est maintenant : quelle structure de données suggéreriez-vous pour stocker les informations : « quand le message a-t-il été lu ? ».
Dans la tâche précédente, nous n'avions besoin que de stocker le fait « oui/non ». Nous devons maintenant stocker la date, et elle ne doit rester en mémoire que jusqu'à ce que le message soit récupéré.
Les dates PS peuvent être stockées en tant qu'objets de la classe Date
intégrée, que nous aborderons plus tard.
Pour stocker une date, on peut utiliser WeakMap
:
laissez messages = [ {texte : "Bonjour", de : "John"}, {texte : "Comment ça va ?", de : "John"}, {texte : "A bientôt", de : "Alice"} ]; laissez readMap = new WeakMap(); readMap.set(messages[0], nouvelle Date(2017, 1, 1)); // Objet date que nous étudierons plus tard