web-dev-qa-db-fra.com

Quelles sont les utilisations réelles de ES6 WeakMap?

Quelles sont les utilisations réelles de la structure de données WeakMap introduite dans ECMAScript 6?

Etant donné qu'une clé d'une carte faible crée une forte référence à sa valeur correspondante, garantissant qu'une valeur insérée dans une carte faible jamais disparaît tant que sa clé est toujours vivante, elle peut ' ne pas être utilisé pour les tableaux de notes, les caches ou toute autre chose pour laquelle vous utiliseriez normalement des références faibles, des cartes avec des valeurs faibles, etc. pour.

Il me semble que ceci:

weakmap.set(key, value);

... est juste une façon détournée de dire ceci:

key.value = value;

Quels cas d'utilisation concrets me manque?

357
valderman

Fondamentalement

Les WeakMaps fournissent un moyen d'étendre des objets de l'extérieur sans interférer avec le garbage collection. Chaque fois que vous voulez étendre un objet mais que vous ne pouvez pas le faire car il est scellé - ou à partir d'une source externe - un WeakMap peut être appliqué .

Un WeakMap est une carte (dictionnaire) où les clés sont faibles - c'est-à-dire si toutes les références à clé sont perdues et qu'il n'y a plus de références à la valeur - le valeur peut être nettoyé. Montrons ceci d'abord à travers des exemples, puis expliquons-le un peu et finissons avec une utilisation réelle.

Disons que j'utilise une API qui me donne un certain objet:

var obj = getObjectFromLibrary();

Maintenant, j'ai une méthode qui utilise l'objet:

function useObj(obj){
   doSomethingWith(obj);
}

Je veux garder une trace de combien de fois la méthode a été appelée avec un certain objet et signaler si cela se produit plus de N fois. Naïvement on penserait utiliser une carte:

var map = new Map(); // maps can have object keys
function useObj(obj){
    doSomethingWith(obj);
    var called = map.get(obj) || 0;
    called++; // called one more time
    if(called > 10) report(); // Report called more than 10 times
    map.set(obj, called);
}

Cela fonctionne, mais il y a une fuite de mémoire - nous gardons maintenant une trace de chaque objet de bibliothèque transmis à la fonction, ce qui empêche les objets de bibliothèque d'être collectés. Au lieu de cela - nous pouvons utiliser un WeakMap:

var map = new WeakMap(); // create a weak map
function useObj(obj){
    doSomethingWith(obj);
    var called = map.get(obj) || 0;
    called++; // called one more time
    if(called > 10) report(); // Report called more than 10 times
    map.set(obj, called);
}

Et la fuite de mémoire est partie.

Cas d'utilisation

Certains cas d'utilisation susceptibles de provoquer une fuite de mémoire et activés par WeakMaps incluent:

  • Conserver des données confidentielles sur un objet spécifique et en donner l'accès uniquement aux personnes référant à la carte. Une approche plus ad hoc consiste à proposer des symboles privés, mais cela prendra du temps.
  • Conserver des données sur les objets de la bibliothèque sans les modifier ni entraîner de surcharge.
  • Conserver des données sur un petit ensemble d'objets contenant de nombreux objets de ce type afin de ne pas avoir de problèmes avec les classes cachées utilisées par les moteurs JS pour les objets du même type.
  • Conserver les données sur les objets de l'hôte tels que les nœuds DOM dans le navigateur.
  • Ajouter une capacité à un objet de l'extérieur (comme l'exemple d'émetteur d'événement dans l'autre réponse).

Regardons une utilisation réelle

Il peut être utilisé pour étendre un objet de l'extérieur. Donnons un exemple pratique (adapté, en quelque sorte réel - pour faire valoir un point) du monde réel de Node.js.

Supposons que vous soyez Node.js et que vous ayez des objets Promise - vous souhaitez maintenant garder une trace de toutes les promesses actuellement rejetées - cependant, vous faites not vous voulez évitez de les ramasser au cas où il n’y aurait aucune référence.

Maintenant, vous don't == voulez ajouter des propriétés à des objets natifs pour des raisons évidentes - vous êtes donc bloqué. Si vous gardez des références aux promesses, vous causez une fuite de mémoire car aucun garbage collection ne peut se produire. Si vous ne conservez pas de références, vous ne pouvez pas enregistrer d'informations supplémentaires sur les promesses individuelles. Tout schéma qui implique l’enregistrement de l’ID d’une promesse signifie de manière inhérente que vous avez besoin d’une référence.

Entrez WeakMaps

WeakMaps signifie que les touches sont faibles. Il n’existe aucun moyen d’énumérer une carte faible ou d’obtenir toutes ses valeurs. Dans une carte faible, vous pouvez stocker les données en fonction d'une clé et, lorsque les clés sont collectées, les valeurs le sont également.

Cela signifie que si vous avez une promesse, vous pouvez stocker un état à ce sujet - et cet objet peut toujours être récupéré. Plus tard, si vous obtenez une référence à un objet, vous pouvez vérifier si vous avez un état le concernant et le signaler.

Ceci a été utilisé pour implémenter crochets de rejet non gérés par Petka Antonov en tant que this :

process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
    // application specific logging, throwing an error, or other logic here
});

Nous conservons les informations sur les promesses sur une carte et pouvons savoir quand une promesse rejetée a été traitée.

453

Cette réponse semble biaisée et inutilisable dans un scénario réel. Veuillez le lire tel quel et ne le considérez pas comme une option réelle pour autre chose que l'expérimentation

Un cas d'utilisation pourrait être de l'utiliser comme dictionnaire pour les auditeurs, j'ai un collègue qui l'a fait. C'est très utile, car tout auditeur est directement concerné par cette façon de faire. Au revoir listener.on.

Mais d'un point de vue plus abstrait, WeakMap est particulièrement puissant pour dématérialiser l'accès à n'importe quoi, vous n'avez pas besoin d'un espace de noms pour isoler ses membres, car il est déjà impliqué dans la nature de cette structure. Je suis presque certain que vous pourriez apporter des améliorations majeures à la mémoire en remplaçant les clés d’objet redondantes (bien que la déconstruction fasse le travail à votre place).


Avant de lire quelle est la prochaine

Je réalise maintenant que mon accent n'est pas exactement la meilleure façon de traiter le problème et, comme Benjamin Gruenbaum l'a fait remarquer (consultez sa réponse, si elle n'est pas déjà au-dessus de la mienne: p), ce problème n'aurait pas pu a été résolu avec un Map régulier, car il aurait coulé. La principale force de WeakMap est donc de ne pas interférer avec le nettoyage des ordures, car ils ne gardent pas de référence.


Voici le code actuel de mon collègue (merci à lui pour le partage)

Source complète ici , il s'agit de la gestion des auditeurs dont j'ai parlé ci-dessus (vous pouvez également jeter un oeil à la specs )

var listenableMap = new WeakMap();


export function getListenable (object) {
    if (!listenableMap.has(object)) {
        listenableMap.set(object, {});
    }

    return listenableMap.get(object);
}


export function getListeners (object, identifier) {
    var listenable = getListenable(object);
    listenable[identifier] = listenable[identifier] || [];

    return listenable[identifier];
}


export function on (object, identifier, listener) {
    var listeners = getListeners(object, identifier);

    listeners.Push(listener);
}


export function removeListener (object, identifier, listener) {
    var listeners = getListeners(object, identifier);

    var index = listeners.indexOf(listener);
    if(index !== -1) {
        listeners.splice(index, 1);
    }
}


export function emit (object, identifier, ...args) {
    var listeners = getListeners(object, identifier);

    for (var listener of listeners) {
        listener.apply(object, args);
    }
}
47
axelduch

WeakMap fonctionne bien pour l'encapsulation et la dissimulation d'informations

WeakMap n'est disponible que pour ES6 et les versions ultérieures. Un WeakMap est un ensemble de paires clé/valeur dans lesquelles la clé doit être un objet. Dans l'exemple suivant, nous construisons un WeakMap avec deux éléments:

var map = new WeakMap();
var pavloHero = {first: "Pavlo", last: "Hero"};
var gabrielFranco = {first: "Gabriel", last: "Franco"};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero));//This is Hero

Nous avons utilisé la méthode set() pour définir une association entre un objet et un autre élément (une chaîne dans notre cas). Nous avons utilisé la méthode get() pour récupérer l'élément associé à un objet. L'aspect intéressant de la WeakMaps est le fait qu'il contient une faible référence à la clé à l'intérieur de la carte. Une référence faible signifie que si l'objet est détruit, le ramasse-miettes supprimera toute l'entrée du fichier WeakMap, libérant ainsi de la mémoire.

var TheatreSeats = (function() {
  var priv = new WeakMap();
  var _ = function(instance) {
    return priv.get(instance);
  };

  return (function() {
      function TheatreSeatsConstructor() {
        var privateMembers = {
          seats: []
        };
        priv.set(this, privateMembers);
        this.maxSize = 10;
      }
      TheatreSeatsConstructor.prototype.placePerson = function(person) {
        _(this).seats.Push(person);
      };
      TheatreSeatsConstructor.prototype.countOccupiedSeats = function() {
        return _(this).seats.length;
      };
      TheatreSeatsConstructor.prototype.isSoldOut = function() {
        return _(this).seats.length >= this.maxSize;
      };
      TheatreSeatsConstructor.prototype.countFreeSeats = function() {
        return this.maxSize - _(this).seats.length;
      };
      return TheatreSeatsConstructor;
    }());
})()
16
Michael Horojanski

J'utilise WeakMap pour la mémoire cache de mémoization sans souci de fonctions prenant en paramètre des objets immuables.

La mémorisation est une manière élégante de dire "après avoir calculé la valeur, mettez-la en cache pour ne pas avoir à la calculer à nouveau".

Voici un exemple:

// using immutable.js from here https://facebook.github.io/immutable-js/

const memo = new WeakMap();

let myObj = Immutable.Map({a: 5, b: 6});

function someLongComputeFunction (someImmutableObj) {
  // if we saved the value, then return it
  if (memo.has(someImmutableObj)) {
    console.log('used memo!');
    return memo.get(someImmutableObj);
  }
  
  // else compute, set, and return
  const computedValue = someImmutableObj.get('a') + someImmutableObj.get('b');
  memo.set(someImmutableObj, computedValue);
  console.log('computed value');
  return computedValue;
}


someLongComputeFunction(myObj);
someLongComputeFunction(myObj);
someLongComputeFunction(myObj);

// reassign
myObj = Immutable.Map({a: 7, b: 8});

someLongComputeFunction(myObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

Quelques points à noter:

  • Les objets Immutable.js renvoient de nouveaux objets (avec un nouveau pointeur) lorsque vous les modifiez. Par conséquent, les utiliser comme clés dans un WeakMap garantit la même valeur calculée.
  • Le WeakMap est idéal pour les mémos car ne fois que l'objet (utilisé comme clé) est récupéré, la valeur calculée sur le WeakMap l'est également.
8
Rico Kahler

?????????????????????????????????

Les cartes faibles peuvent être utilisées pour stocker des métadonnées sur les éléments du DOM sans interférer avec le nettoyage des ordures ni rendre les collègues en colère contre votre code. Par exemple, vous pouvez les utiliser pour indexer numériquement tous les éléments d’une page Web.

????????????????????????????? ????????????????????????????????? ???????? ??????????????????????????????????:

var elements = document.getElementsByTagName('*'),
  i = -1, len = elements.length;

while (++i !== len) {
  // Production code written this poorly makes me want to cry:
  elements[i].lookupindex = i;
  elements[i].elementref = [];
  elements[i].elementref.Push( elements[Math.pow(i, 2) % len] );
}

// Then, you can access the lookupindex's
// For those of you new to javascirpt, I hope the comments below help explain 
// how the ternary operator (?:) works like an inline if-statement
document.write(document.body.lookupindex + '<br />' + (
    (document.body.elementref.indexOf(document.currentScript) !== -1)
    ? // if(document.body.elementref.indexOf(document.currentScript) !== -1){
    "true"
    : // } else {
    "false"
  )   // }
);

???????????????????? ????????????????????????????????? ???????????? ??????????????????????????????????:

var DOMref = new WeakMap(),
  __DOMref_value = Array,
  __DOMref_lookupindex = 0,
  __DOMref_otherelement = 1,
  elements = document.getElementsByTagName('*'),
  i = -1, len = elements.length, cur;

while (++i !== len) {
  // Production code written this greatly makes me want to ????:
  cur = DOMref.get(elements[i]);
  if (cur === undefined)
    DOMref.set(elements[i], cur = new __DOMref_value)

  cur[__DOMref_lookupindex] = i;
  cur[__DOMref_otherelement] = new WeakSet();
  cur[__DOMref_otherelement].add( elements[Math.pow(i, 2) % len] );
}

// Then, you can access the lookupindex's
cur = DOMref.get(document.body)
document.write(cur[__DOMref_lookupindex] + '<br />' + (
    cur[__DOMref_otherelement].has(document.currentScript)
    ? // if(cur[__DOMref_otherelement].has(document.currentScript)){
    "true"
    : // } else {
    "false"
  )   // }
);

???????????? ?????????????????????????????????????????

La différence peut paraître négligeable, mis à part le fait que la version faible est plus longue, mais il existe une différence majeure entre les deux éléments de code présentés ci-dessus. Dans le premier extrait de code, sans mappes faibles, le morceau de code stocke les références dans tous les sens entre les éléments DOM. Cela empêche les éléments DOM d'être collectés. Math.pow(i, 2) % len] peut sembler être une chose bizarre que personne ne voudrait utiliser, mais détrompez-vous: beaucoup de code de production contient des références DOM qui rebondissent dans tout le document. Maintenant, pour le deuxième morceau de code, parce que toutes les références aux éléments sont faibles, lorsque vous supprimez un nœud, le navigateur est en mesure de déterminer que le nœud n’est pas utilisé (votre code ne peut l’atteindre), et supprimez-le donc de la mémoire. La raison pour laquelle vous devriez vous préoccuper de l’utilisation de la mémoire et des ancres de mémoire (comme le premier fragment de code dans lequel les éléments inutilisés sont conservés en mémoire) tient au fait que plus d’utilisation de la mémoire signifie plus de tentatives de GC dans le navigateur (pour essayer de libérer de la mémoire). éviter une panne du navigateur) signifie une expérience de navigation plus lente et parfois une panne du navigateur.

En ce qui concerne un polyfill pour ceux-ci, je recommanderais ma propre bibliothèque ( trouvé ici @ github ). C'est une bibliothèque très légère qui va simplement la polyfiller sans aucun des frameworks trop complexes que vous pourriez trouver dans d'autres polyfills.

~ Joyeux codage!

5
Jack Giffin