Каковы фактические применения ES6 WeakMap?


каковы фактические применения WeakMap структура данных, введенная в ECMAScript 6?

поскольку ключ слабой карты создает сильную ссылку на ее соответствующее значение, гарантируя, что значение, которое было вставлено в слабую карту, будет никогда исчезает до тех пор, пока его ключ все еще жив, он не может использоваться для таблиц заметок, кэшей или чего-либо еще, что вы обычно используете слабые ссылки, карты со слабыми значениями и т. д. для.

мне кажется, вот это:

weakmap.set(key, value);

...это просто окольный способ сказать это:

key.value = value;

какие конкретные случаи использования я пропустил?

5 303

5 ответов:

принципиально

WeakMaps обеспечивают способ расширения объектов извне без вмешательства в сборку мусора. всякий раз, когда вы хотите расширить объект, но не можете, потому что он запечатан - или из внешнего источника - можно применить WeakMap.

слабая карта-это карта (словарь), где ключи слабы - то есть если все ссылки на ключ теряются и больше нет ссылок на значение - элемент стоимостью может быть собран "мусор". Давайте сначала покажем это на примерах, затем немного объясним и, наконец, закончим с реальным использованием.

допустим я использую API, который дает мне определенный объект:

var obj = getObjectFromLibrary();

теперь у меня есть метод, который использует объект:

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

Я хочу отслеживать, сколько раз метод вызывался с определенным объектом и сообщать, если это происходит более N раз. Наивно можно было бы подумать, чтобы использовать Карта:

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

это работает, но у него есть утечка памяти - теперь мы отслеживаем каждый объект библиотеки, переданный функции, которая удерживает объекты библиотеки от сбора мусора. Вместо этого мы можем использовать 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);
}

и утечка памяти исчезла.

варианты использования

некоторые варианты использования, которые в противном случае вызвали бы утечку памяти и включены WeakMap s включают в себя:

  • сохранение личных данных о конкретном объекте и только предоставление доступа к нему людям со ссылкой на карту. Более специальный подход приходит с предложением частных символов, но это уже давно.
  • хранение данных об объектах библиотеки без их изменения или накладных расходов.
  • хранение данных о небольшом наборе объектов, где существует много объектов этого типа, чтобы не возникало проблем со скрытыми классами JS-движки используют для объектов одного и того же типа тип.
  • хранение данных об объектах хоста, таких как узлы DOM, в браузере.
  • добавление возможности к объекту извне (например, пример эмиттера событий в другом ответе).

давайте посмотрим на реальное применение

он может быть использован для расширения объекта извне. Приведем практический (адаптированный, вроде бы реальный - чтобы сделать точку) пример из реального мира Node.js.

допустим, вы узел.js и у вас есть Promise объекты-теперь вы хотите отслеживать все отвергнутые в настоящее время обещания - однако вы делаете не хотите, чтобы они не были мусором, собранным в случае отсутствия ссылок на них.

вы не хотите добавить свойства к собственным объектам по очевидным причинам-так что вы застряли. Если вы сохраняете ссылки на обещания, вы вызываете утечку памяти, так как сбор мусора не может произойти. Если вы не держите ссылки, то вы не можете сохранить дополнительная информация об отдельных обещаниях. Любая схема, включающая сохранение идентификатора обещания, по своей сути означает, что вам нужна ссылка на него.

Введите WeakMaps

слабые карты означают, что ключи слабые. Нет способов перечислить слабую карту или получить все ее значения. В слабой карте вы можете хранить данные на основе ключа, и когда ключ получает сбор мусора, так же как и значения.

это означает, что данное обещание вы можете хранить заявите об этом - и этот объект все равно может быть собран мусором. Позже, если вы получите ссылку на объект, вы можете проверить, есть ли у вас какое-либо состояние, связанное с ним, и сообщить об этом.

это было использовано для реализации необработанные крючки отклонения Петька Антонов как этой:

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

мы храним информацию об обещаниях на карте и можем знать, когда было обработано отклоненное обещание.

этот ответ кажется предвзятым и непригодным для использования в реальном мире. Пожалуйста, прочитайте его как есть, и не рассматривайте его как фактический вариант для чего-либо еще, кроме экспериментов

прецедент может быть использовать его в качестве словаря для слушателей, у меня есть коллега, который сделал это. Это очень полезно, потому что любой слушатель непосредственно нацелен на этот способ делать вещи. До свидания listener.on.

но с более абстрактной точки зрения, WeakMap особенно мощным в дематериализоваться доступ к в принципе ничего, вам не нужно пространство, чтобы изолировать своих членов, так как это уже подразумевается характер этой структуры. Я уверен, что вы можете сделать некоторые значительные улучшения памяти, заменив неловкие избыточные ключи объектов (хотя деконструкция делает работу за вас).


прежде чем читать, что дальше

теперь я понимаю, что мой акцент не совсем лучший способ решить эту проблему и как Бенджамин Gruenbaum указал (проверьте его ответ, если он еще не выше моего :p), эта проблема не могла быть решена с помощью обычного Map, так как он бы протекал, таким образом, основная сила WeakMap заключается в том, что он не мешает сборке мусора, учитывая, что они не сохранила ссылку.


вот фактический код моего коллеги (спасибо его для совместного использования)

полный источник здесь, речь идет о слушателях управление я говорил выше (вы также можете взглянуть на технические характеристики)

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

WeakMap хорошо работает для инкапсуляции и сокрытия информации

WeakMap доступно только для ES6 и выше. А WeakMap представляет собой набор пар ключ и значение, где ключ должен быть объектом. В следующем примере мы построим WeakMap два элемента:

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

мы использовали set() метод для определения связи между объектом и другим элементом (строка в нашем случае). Мы использовали get() метод извлечения элемента, связанного с объектом. Интересный аспект WeakMaps-это тот факт, что он содержит слабую ссылку на ключ внутри карты. Слабая ссылка означает, что если объект будет уничтожен, сборщик мусора удалит все записи из WeakMap, тем самым освобождая память.

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

Я использую WeakMap для кэша беззаботного запоминания функций, которые принимают неизменяемые объекты в качестве своего параметра.

Memoization-это причудливый способ сказать:"после того, как вы вычислите значение, кэшируйте его, чтобы вам не пришлось вычислять его снова".

вот пример:

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

дополнительная информация:

  • незыблемыми.объекты js возвращают новые объекты (с новым указателем) , когда вы изменяете их так использование их в качестве ключей в слабой карте гарантирует одно и то же вычисленное значение.
  • The WeakMap отлично подходит для заметок, потому что как только объект (используемый в качестве ключа) получает собранный мусор, так и вычисленное значение на WeakMap.

слабые карты можно использовать для хранения метаданных об элементах DOM, не вмешиваясь в сборку мусора или не заставляя коллег злиться на ваш код. Например, их можно использовать для числового индексирования всех элементов веб-страницы.

без WeakMaps или WeakSets:

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

использование WeakMaps и 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"
  )   // }
);

Разница

разница может выглядеть незначительной, помимо дело в том, что версия weakmap длиннее, однако есть большая разница между двумя частями кода, показанными выше. В первом фрагменте кода, без слабых карт, часть кода хранит ссылки в любом направлении между элементами DOM. Это предотвращает сбор мусора из элементов DOM. Math.pow(i, 2) % len] может показаться странным, что никто не будет использовать, но подумайте еще раз: много производственного кода имеет ссылки DOM, которые отскакивают по всему документу. Теперь, для второго куска кода, поскольку все ссылки на элементы являются слабыми, когда вы удаляете узел, браузер может определить, что узел не используется (не может быть достигнут вашим кодом), и, таким образом, удалить его из памяти. Причина, по которой вы должны быть обеспокоены использованием памяти, а якоря памяти (такие вещи, как первый фрагмент кода, где неиспользуемые элементы хранятся в памяти), заключается в том, что больше использования памяти означает больше попыток GC браузера (чтобы попытаться освободить память, чтобы предотвратить сбой браузера) означает медленнее опыт просмотра, а иногда и сбой браузера.

Что касается полифилл для них, я бы рекомендовал свою собственную библиотеку ( найдено здесь @ github). Это очень легкая библиотека, которая просто заполнит ее без каких-либо чрезмерно сложных фреймворков, которые вы можете найти в других полипах.

~ удачи в кодировании!