В каких случаях используется структура данных WeakMap
, представленная в ECMAScript 6?
Поскольку ключ слабой карты создает сильную ссылку на соответствующее значение, гарантируя, что значение, которое было вставлено в слабую карту, никогда не исчезнет, пока жив его ключ, она не может быть использована для мемо-таблиц, кэшей или чего-либо еще, для чего вы обычно используете слабые ссылки, карты со слабыми значениями и т.д..
Мне кажется, что это:
weakmap.set(key, value);
...это просто окольный путь, чтобы сказать это:
key.value = value;
Какие конкретные примеры использования я упускаю?
**Когда вы хотите расширить объект, но не можете, потому что он запечатан - или из внешнего источника - можно применить WeakMap.
WeakMap - это карта (словарь), в которой _ключи являются слабыми - то есть, если все ссылки на ключ будут потеряны и больше не будет ссылок на значение, то значение можно будет собирать в мусор. Давайте сначала покажем это на примерах, затем немного объясним и, наконец, закончим реальным применением.
Допустим, я использую API, который предоставляет мне определенный объект:
var obj = getObjectFromLibrary();
Теперь у меня есть метод, который использует этот объект:
function useObj(obj){
doSomethingWith(obj);
}
Я хочу отслеживать, сколько раз метод был вызван с определенным объектом, и сообщать, если это происходит более N раз. Наивно полагать, что для этого нужно использовать Map:
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
, включают:
Это можно использовать для расширения объекта извне. Приведем практический (адаптированный, как бы реальный - для наглядности) пример из реального мира Node.js.
Допустим, вы Node.js и у вас есть объекты Promise
- теперь вы хотите отслеживать все отклоненные в данный момент обещания - однако, вы не хотите, чтобы они не собирались в мусор, если на них нет ссылок.
Теперь вы не хотите добавлять свойства к родным объектам по очевидным причинам - так что вы застряли. Если вы сохраняете ссылки на обещания, вы вызываете утечку памяти, поскольку сборка мусора не может произойти. Если вы не храните ссылки, то вы не можете сохранить дополнительную информацию об отдельных обещаниях. Любая схема, включающая сохранение ID обещания, по своей сути означает, что вам нужна ссылка на него.
Слабые карты означают, что _ключи являются слабыми. Не существует способов перечислить слабую карту или получить все ее значения. В слабой карте вы можете хранить данные на основе ключа, а когда ключ будет собран, то и значения тоже.
Это означает, что, получив обещание, вы можете хранить состояние о нем - и этот объект все равно может быть собран. Позже, если вы получите ссылку на объект, вы можете проверить, есть ли у вас состояние, относящееся к нему, и сообщить об этом.
Это было использовано для реализации unhandled rejection hooks Петькой Антоновым как 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
});
Мы храним информацию об обещаниях в карте и можем знать, когда отвергнутое обещание было обработано.
Примером использования может быть использование его в качестве словаря для слушателей, у меня есть коллега, который сделал это. Это очень полезно, потому что любой слушатель напрямую нацелен на этот способ. Прощай listener.on
.
Но с более абстрактной точки зрения, WeakMap
является особенно мощным для дематериализации доступа к практически чему угодно, вам не нужно пространство имен для изоляции его членов, поскольку это уже подразумевается природой этой структуры. Я уверен, что вы можете добиться значительного улучшения памяти, заменив неудобные избыточные ключи объектов (хотя деконструкция делает эту работу за вас).
Теперь я понимаю, что мой акцент не совсем лучший способ решения проблемы, и как отметил Benjamin 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()
для получения элемента, связанного с объектом. Интересным аспектом 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;
}());
})()
Я использую WeakMap для кэш беспокоиться-бесплатно мемоизация функций, которые принимают в постоянные объекты, как их параметр.
Мемоизация-это причудливый способ сказать, что "после того, как вы вычислить значение, кэшировать его, так что вы Дон'т иметь, чтобы вычислить его снова".
Здесь'ы пример:
в
// 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>
в
Несколько вещей, чтобы отметить:
Слабые карты могут быть использованы для хранения метаданных элементов 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"
) // }
);
в
в
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, быть собранным "мусор". Математика.тыц(я, 2) % лен]` может показаться странным, что никто не будет использовать, но подумайте еще раз: много производственном коде есть ссылки дом, которые отражаются во всем документе. Итак, второй кусок кода, потому что все ссылки на элементы слабы, когда вам удалить узел, браузер может определить, что узел не используется (не могут быть достигнуты с помощью вашего кода), и таким образом удалить его из памяти. Причина, почему вы должны заботиться о памяти, памяти и якоря (такие вещи, как первый фрагмент кода, где неиспользуемые элементы хранятся в памяти) потому что больше памяти означает более браузере ГХ-попытки (чтобы попытаться освободить память для предотвращения сбоя браузера) означает меньший опыт просмотра и иногда сбоя браузера.
Как на полифилл для этих, я бы порекомендовал моей собственной библиотеке (нашли здесь @ на GitHub). Это очень легкая библиотека, которая просто полифилл его без каких-либо пути-сложные механизмы вы можете найти в других полифиллы.
в
~ Удачи в кодировании!