Jakie są rzeczywiste zastosowania ES6 WeakMap?

Jakie są rzeczywiste zastosowania struktury danych WeakMap wprowadzonej w ECMAScript 6?

Ponieważ klucz słabej mapy tworzy silne odniesienie do odpowiadającej jej wartości, zapewniając, że wartość, która została wstawiona do słabej mapy, Nigdy nie zniknie tak długo, jak jej klucz jest nadal żywy, nie może być używany do tabel memo, pamięci podręcznych lub czegokolwiek innego, czego normalnie używałbyś słabych odniesień, map o słabych wartościach itp. za.

Wydaje mi się, że to:

weakmap.set(key, value);

... jest tylko okrężnym sposobem powiedzenia tego:

key.value = value;

Jakich konkretnych przypadków użycia brakuje?

Author: valderman, 2015-04-02

5 answers

Weakmapy umożliwiają rozszerzanie obiektów z zewnątrz bez ingerencji w usuwanie śmieci. Gdy chcesz rozszerzyć obiekt, ale nie możesz, ponieważ jest on zapieczętowany - lub z zewnętrznego źródła - można zastosować słabą mapę.

WeakMap jest mapą (słownikiem), na której klucze są słabe - to znaczy, jeśli wszystkie odniesienia do klucza są utracone i nie ma już odniesień do wartości-wartość może być śmieci zebrane. Pokażmy to najpierw na przykładach, następnie wyjaśnijmy to trochę i na koniec zakończmy prawdziwym użyciem.

Załóżmy, że używam API, które daje mi określony obiekt:

var obj = getObjectFromLibrary();

Teraz mam metodę, która używa obiektu:

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

Chcę śledzić, ile razy metoda została wywołana z określonym obiektem i raportować, czy dzieje się więcej niż n razy. Naiwnie można by pomyśleć o użyciu mapy:

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);
}

To działa, ale ma wyciek pamięci-my teraz śledź każdy pojedynczy obiekt biblioteki przekazany do funkcji, która chroni obiekty biblioteki przed zbieraniem śmieci. Zamiast - możemy użyć 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);
}

I wyciek pamięci zniknął.

Przypadki użycia

Niektóre przypadki użycia, które w przeciwnym razie spowodowałyby wyciek pamięci i są włączone przez WeakMap S, obejmują:

  • przechowywanie prywatnych danych o konkretnym obiekcie i udostępnienie go tylko osobom z odniesieniem do mapy. Podejście bardziej ad hoc nadchodzi z propozycją symboli prywatnych, ale to już za dużo czasu.
  • przechowywanie danych o obiektach bibliotecznych bez ich zmiany lub ponoszenia kosztów.
  • przechowywanie danych o małym zestawie obiektów, w których istnieje wiele obiektów tego typu, aby nie powodować problemów z ukrytymi klasami używanymi przez silniki JS dla obiektów tego samego typu.
  • przechowywanie danych o obiektach hosta, takich jak węzły DOM w przeglądarce.
  • dodawanie możliwości do obiektu z zewnątrz (jak przykład emitera zdarzeń w drugiej odpowiedzi).

Spójrzmy na prawdziwe użycie

Może być używany do rozszerzenia obiektu z zewnątrz. Dajmy praktyczny (zaadaptowany, jakby realny - aby punkt) przykład ze świata rzeczywistego Node.js.

Powiedzmy, że jesteś węzłem.js i masz Promise obiekty - teraz chcesz śledzić wszystkie aktualnie odrzucone obietnice - jednak nie chcesz , a nie, aby nie były zbierane śmieci w przypadku, gdy nie istnieją żadne odniesienia za nich.

Teraz, nie chcesz dodawać właściwości do natywnych obiektów z oczywistych powodów - więc utknąłeś. Jeśli zachowasz odniesienia do obietnic, spowodujesz wyciek pamięci, ponieważ nie może się zdarzyć żadne zbieranie śmieci. Jeśli nie przechowujesz referencji, nie możesz zapisać dodatkowych informacji o poszczególnych obietnicach. Każdy schemat, który polega na zapisaniu ID obietnicy z natury oznacza, że potrzebujesz odniesienia do niej.

Enter WeakMaps

Słabe Mapy oznaczają, że klucze są słabe. Nie ma możliwości wyliczenia słabej mapy lub uzyskania wszystkich jej wartości. W słabej mapie można przechowywać dane na podstawie klucza, a gdy klucz pobiera śmieci, tak samo wartości.

Oznacza to, że przy danej obietnicy można przechowywać stan na jej temat - a ten obiekt nadal może być zbierany jako śmieci. Później, jeśli otrzymasz odniesienie do obiektu, możesz sprawdzić, czy masz jakiś stan z nim związany i zgłosić go.

To zostało użyte do zaimplementowania odrzucenie haków by Petka Antonov as 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
});

Przechowujemy informacje o obietnicach na mapie i możemy wiedzieć, kiedy odrzucona obietnica została rozpatrzona.

 404
Author: Benjamin Gruenbaum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-22 17:53:30

Ta odpowiedź wydaje się być stronnicza i bezużyteczna w realnym świecie. Przeczytaj to tak, jak jest, i nie traktuj tego jako faktycznej opcji dla czegokolwiek innego niż eksperymentowanie

Przypadkiem użycia może być użycie go jako słownika dla słuchaczy, mam współpracownika, który to zrobił. Jest to bardzo pomocne, ponieważ każdy słuchacz jest bezpośrednio ukierunkowany na ten sposób robienia rzeczy. Żegnaj listener.on.

Ale z bardziej abstrakcyjnego punktu widzenia, WeakMap jest szczególnie potężny, aby zdematerializować dostęp do w zasadzie nic, nie potrzebujesz przestrzeni nazw, aby odizolować jej członków, ponieważ jest to już sugerowane przez naturę tej struktury. Jestem całkiem pewien, że można zrobić kilka poważnych ulepszeń pamięci, zastępując awkwards nadmiarowe klucze obiektów(nawet jeśli dekonstruowanie działa za Ciebie).


Przed przeczytaniem co dalej

Teraz zdaję sobie sprawę, że moje podkreślenie nie jest najlepszym sposobem rozwiązania problemu i jak zauważył Benjamin Gruenbaum (sprawdź jego odpowiedź, jeśli nie jest już powyżej mojego :p), ten problem nie mógłby zostać rozwiązany za pomocą zwykłego Map, ponieważ wyciekłby, a więc główną siłą WeakMap jest to, że nie koliduje z usuwaniem śmieci, biorąc pod uwagę, że nie przechowują odniesienia.


Oto prawdziwy kod mojego współpracownika (dzięki jemu za dzielenie się)

Pełne źródło tutaj, chodzi o zarządzanie słuchaczami, o którym mówiłem powyżej (możesz również spojrzeć na 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);
    }
}
 44
Author: axelduch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-18 18:50:30

WeakMap działa dobrze do enkapsulacji i ukrywania informacji

WeakMap jest dostępny tylko dla ES6 i nowszych. A WeakMap jest zbiorem par klucz i wartość, gdzie klucz musi być obiektem. W poniższym przykładzie budujemy WeakMap z dwóch elementów:

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

Użyliśmy metody set() do zdefiniowania asocjacji między obiektem a innym elementem (w naszym przypadku łańcuch znaków). Użyliśmy metody get() do pobrania elementu powiązanego z obiektem. Ciekawy aspekt WeakMaps to fakt, że posiada słabe odniesienie do klucza wewnątrz mapy. Słabe odniesienie oznacza, że jeśli obiekt zostanie zniszczony, garbage collector usunie cały wpis z WeakMap, uwalniając w ten sposób pamięć.

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;
    }());
})()
 11
Author: Michael Horojanski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 20:12:36

Używam WeakMap do buforowania bezproblemowej memoizacji funkcji, które przyjmują jako swój parametr obiekty niezmienne.

Memoizacja jest wymyślnym sposobem na powiedzenie "po obliczeniu wartości, buforuj ją, więc nie musisz jej ponownie obliczać".

Oto przykład:

// 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>

Kilka rzeczy do zapamiętania:

  • niezmienny.obiekty js zwracają nowe obiekty (z nowym wskaźnikiem) podczas ich modyfikowania, więc używanie ich jako kluczy w Weakmapie jest gwarancją ta sama obliczona wartość.
  • WeakMap jest świetny do notatek, ponieważ gdy obiekt (używany jako klucz) pobiera śmieci, tak samo jak obliczona wartość na Weakmapie.
 6
Author: Rico Kahler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-18 20:56:23

Słabe mapy mogą być używane do przechowywania metadanych o elementach DOM bez zakłócania zbierania śmieci lub wkurzania współpracowników na Twój kod. Można na przykład użyć ich do numerycznego indeksowania wszystkich elementów na stronie internetowej.

Bez słabych i słabych stron:

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"
  )   // }
);

Używanie WeakMap i WeakSets:

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"
  )   // }
);

Różnica

Różnica może wyglądać na znikomą, pomijając fakt, że wersja weakmap jest dłuższa, jednak istnieje duża różnica między dwoma fragmentami kodu pokazanymi powyżej. W pierwszym fragmencie kodu, bez słabych map, fragment kodu przechowuje odniesienia pomiędzy elementami DOM. Zapobiega to zbieraniu śmieci z elementów DOM. Math.pow(i, 2) % len] może wydawać się dziwakiem, którego nikt by nie użył, ale pomyśl jeszcze raz: wiele kodu produkcyjnego ma odwołania do DOM, które odbijają się po całym dokumencie. Teraz, dla drugiego kawałka kodu, ponieważ wszystkie odniesienia do elementów są słabe, gdy usuniesz węzeł, przeglądarka jest w stanie określić, że węzeł nie jest używany (nie można do niego dotrzeć za pomocą kodu), a tym samym usunąć go z pamięci. Powodem, dla którego powinieneś martwić się o zużycie pamięci i kotwice pamięci (takie jak pierwszy fragment kodu, w którym nieużywane elementy są przechowywane w pamięci), jest to, że większe zużycie pamięci oznacza więcej prób GC przeglądarki (aby spróbować zwolnić pamięć, aby uniknąć awarii przeglądarki) oznacza wolniejsze przeglądanie, a czasami przeglądarka crash.

Jeśli chodzi o polyfill do tych, polecam własną bibliotekę (znajdującą się tutaj @ github ). Jest to bardzo lekka biblioteka, która po prostu wypełni ją bez zbyt skomplikowanych frameworków, które można znaleźć w innych polyfillach.

~ Happy coding!
 3
Author: Jack Giffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-08 18:07:18