Каково фактическое использование ES6 WeakMap?

Каково фактическое использование структуры данных WeakMap, введенной в ECMAScript 6?

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

Мне кажется, что это:

weakmap.set(key, value);

... это просто окольный способ сказать следующее:

key.value = value;

Какие конкретные варианты использования мне не хватает?

Ответ 1

В корне

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

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:

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

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

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

Допустим, вы Node.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
});

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

Ответ 2

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

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

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


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

Теперь я понимаю, что мой акцент не совсем лучший способ решения проблемы, и, как указал Бенджамин Грюнбаум (посмотрите его ответ, если он еще не выше моего: 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);
    }
}

Ответ 3

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

WeakMap доступен только для ES6 и выше. A 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() для извлечения элемента, связанного с объектом. Интересным аспектом WeakMap является тот факт, что он содержит слабую ссылку на ключ внутри карты. Слабая ссылка означает, что если объект уничтожен, сборщик мусора удалит всю запись из 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;
    }());
})()

Ответ 4

Я использую 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>

Ответ 5

𝗠𝗲𝘁𝗮𝗱𝗮𝘁𝗮

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

𝗪𝗲𝗮𝗸𝗦𝗲𝘁𝘀 𝗪𝗲𝗮𝗸𝗠𝗮𝗽𝘀 𝗼𝗿 𝗪𝗲𝗮𝗸𝗦𝗲𝘁𝘀:

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

Ответ 6

(В настоящее время редактирование ответа...)