Каковы фактические использования ES6 WeakMap?
Каковы фактические использования WeakMap
структура данных введена в ECMAScript 6?
Так как ключ слабой карты создает сильную ссылку на его соответствующее значение, гарантируя, что значение, которое было вставлено в слабую карту, никогда не исчезнет, пока его ключ еще жив, его нельзя использовать для памятных таблиц, кэши или что-то еще, для чего вы обычно используете слабые ссылки, карты со слабыми значениями и т. д.
Мне кажется, что это:
weakmap.set(key, value);
... это просто окольный способ сказать это:
key.value = value;
Какие конкретные варианты использования я пропускаю?
8 ответов
В корне
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
s включают в себя:
- Хранение личных данных о конкретном объекте и предоставление доступа к нему только людям со ссылкой на карту. Более специальный подход идет с предложением частных символов, но это еще долго.
- Хранение данных об объектах библиотеки без их изменения или накладных расходов.
- Хранение данных о небольшом наборе объектов, где существует много объектов этого типа, чтобы не было проблем со скрытыми классами, которые движки 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
});
Мы храним информацию об обещаниях на карте и можем знать, когда было выполнено отклоненное обещание.
Этот ответ кажется предвзятым и непригодным для сценария реального мира. Пожалуйста, прочитайте это как есть, и не рассматривайте это как актуальный вариант для чего-либо еще, кроме экспериментов
Вариант использования может состоять в том, чтобы использовать его как словарь для слушателей, у меня есть сотрудник, который сделал это. Это очень полезно, потому что любой слушатель напрямую нацелен на такой способ работы. Прощай listener.on
,
Но с более абстрактной точки зрения, WeakMap
особенно мощно дематериализовать доступ практически ко всему, вам не нужно пространство имен, чтобы изолировать его членов, поскольку это уже подразумевается природой этой структуры. Я почти уверен, что вы могли бы сделать некоторые значительные улучшения памяти, заменив неудобные избыточные ключи объектов (даже если деконструкция работает для вас).
Прежде чем читать, что дальше
Теперь я понимаю, что мой акцент не совсем лучший способ решения проблемы, и, как отметил Benjamin Gruenbaum (посмотрите его ответ, если он еще не выше моего:p), эту проблему нельзя было бы решить с помощью обычного Map
, так как это утекло бы, таким образом, основная сила WeakMap
в том, что он не мешает сбору мусора, учитывая, что они не сохраняют ссылку.
Вот актуальный код моего сотрудника (спасибо Hugeen за то, что поделился)
Полный источник здесь, это об управлении слушателями, о котором я говорил выше (вы также можете взглянуть на спецификации)
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
s - это тот факт, что он содержит слабую ссылку на ключ внутри карты. Слабая ссылка означает, что если объект уничтожен, сборщик мусора удалит всю запись из 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;
}());
})()
Слабые карты можно использовать для хранения метаданных об элементах 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"
) // }
);
Различия
Разница может показаться незначительной, за исключением того, что версия слабой карты длиннее, однако между двумя фрагментами кода, показанными выше, есть существенная разница. В первом фрагменте кода, без слабых отображений, фрагмент кода хранит ссылки в разные стороны между элементами DOM. Это предотвращает сборку мусора элементами DOM. Math.pow(i, 2) % len]
может показаться странным, что никто бы не использовал, но подумайте еще раз: во многих производственных кодах есть ссылки на DOM, которые отражаются по всему документу. Теперь для второго фрагмента кода, поскольку все ссылки на элементы являются слабыми, когда вы удаляете узел, браузер может определить, что узел не используется (не может быть достигнут вашим кодом), и таким образом удалите это из памяти. Причина, по которой вы должны быть обеспокоены использованием памяти и якорями памяти (такими как первый фрагмент кода, где неиспользуемые элементы хранятся в памяти), заключается в том, что большее использование памяти означает больше попыток GC браузера (чтобы попытаться освободить память для предотвращение сбоя браузера) означает более медленный просмотр и иногда сбой браузера.
Что касается polyfill для них, я бы порекомендовал мою собственную библиотеку ( находится здесь @ github). Это очень легкая библиотека, которая просто заполняет ее без каких-либо слишком сложных сред, которые вы можете найти в других полизаполнениях.
~ Счастливого кодирования!
Я использую 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>
Несколько вещей, на которые стоит обратить внимание:
- Объекты Immutable.js возвращают новые объекты (с новым указателем), когда вы изменяете их, поэтому использование их в качестве ключей в WeakMap гарантирует то же вычисленное значение.
- WeakMap отлично подходит для заметок, потому что, как только объект (используемый в качестве ключа) получает сборщик мусора, то же самое происходит и с вычисленным значением в WeakMap.
У меня есть этот простой пример использования / пример для WeakMaps.
УПРАВЛЯЙТЕ КОЛЛЕКЦИЕЙ ПОЛЬЗОВАТЕЛЕЙ
Я начал с User
Объект, свойства которого включают fullname
, username
, age
, gender
и метод под названием print
который печатает понятную человеку сводку других свойств.
/**
Basic User Object with common properties.
*/
function User(username, fullname, age, gender) {
this.username = username;
this.fullname = fullname;
this.age = age;
this.gender = gender;
this.print = () => console.log(`${this.fullname} is a ${age} year old ${gender}`);
}
Затем я добавил карту под названием users
для хранения коллекции нескольких пользователей, которые имеют ключи username
.
/**
Collection of Users, keyed by username.
*/
var users = new Map();
Добавление коллекции также потребовало вспомогательных функций для добавления, получения, удаления пользователя и даже функции для печати всех пользователей для полноты.
/**
Creates an User Object and adds it to the users Collection.
*/
var addUser = (username, fullname, age, gender) => {
let an_user = new User(username, fullname, age, gender);
users.set(username, an_user);
}
/**
Returns an User Object associated with the given username in the Collection.
*/
var getUser = (username) => {
return users.get(username);
}
/**
Deletes an User Object associated with the given username in the Collection.
*/
var deleteUser = (username) => {
users.delete(username);
}
/**
Prints summary of all the User Objects in the Collection.
*/
var printUsers = () => {
users.forEach((user) => {
user.print();
});
}
Когда весь приведенный выше код работает, скажем, в NodeJS, толькоusers
Карта имеет ссылку на пользовательские объекты в рамках всего процесса. Других ссылок на отдельные пользовательские объекты нет.
Запустив этот код в интерактивной оболочке NodeJS, я просто в качестве примера добавляю четырех пользователей и распечатываю их:
ДОБАВИТЬ ДОПОЛНИТЕЛЬНУЮ ИНФОРМАЦИЮ ДЛЯ ПОЛЬЗОВАТЕЛЕЙ БЕЗ ИЗМЕНЕНИЯ СУЩЕСТВЕННОГО КОДА
Теперь предположим, что требуется новая функция, в которой ссылки на платформу социальных сетей (SMP) каждого пользователя должны отслеживаться вместе с пользовательскими объектами.
Ключевым моментом здесь также является то, что эта функция должна быть реализована с минимальным вмешательством в существующий код.
Это возможно с помощью WeakMaps следующим образом.
Я добавляю три отдельных WeakMaps для Twitter, Facebook, LinkedIn.
/*
WeakMaps for Social Media Platforms (SMPs).
Could be replaced by a single Map which can grow
dynamically based on different SMP names . . . anyway...
*/
var sm_platform_twitter = new WeakMap();
var sm_platform_facebook = new WeakMap();
var sm_platform_linkedin = new WeakMap();
Вспомогательная функция, getSMPWeakMap
добавляется просто для возврата WeakMap, связанного с заданным именем SMP.
/**
Returns the WeakMap for the given SMP.
*/
var getSMPWeakMap = (sm_platform) => {
if(sm_platform == "Twitter") {
return sm_platform_twitter;
}
else if(sm_platform == "Facebook") {
return sm_platform_facebook;
}
else if(sm_platform == "LinkedIn") {
return sm_platform_linkedin;
}
return undefined;
}
Функция для добавления SMP-ссылки пользователей к заданной SMP WeakMap.
/**
Adds a SMP link associated with a given User. The User must be already added to the Collection.
*/
var addUserSocialMediaLink = (username, sm_platform, sm_link) => {
let user = getUser(username);
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
if(user && sm_platform_weakmap) {
sm_platform_weakmap.set(user, sm_link);
}
}
Функция для печати только тех пользователей, которые присутствуют на данном SMP.
/**
Prints the User's fullname and corresponding SMP link of only those Users which are on the given SMP.
*/
var printSMPUsers = (sm_platform) => {
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
console.log(`Users of ${sm_platform}:`)
users.forEach((user)=>{
if(sm_platform_weakmap.has(user)) {
console.log(`\t${user.fullname} : ${sm_platform_weakmap.get(user)}`)
}
});
}
Теперь вы можете добавлять ссылки SMP для пользователей, также с возможностью, чтобы каждый пользователь имел ссылку на нескольких SMP.
... продолжая предыдущий пример, я добавляю ссылки SMP для пользователей, несколько ссылок для пользователей Билла и Сары, а затем распечатываю ссылки для каждого SMP отдельно:
Теперь предположим, что Пользователь удален из users
Карта по телефону deleteUser
. Это удаляет единственную ссылку на объект пользователя. Это, в свою очередь, также удалит ссылку SMP из любых / всех WeakMaps SMP (посредством сборки мусора), поскольку без объекта пользователя нет возможности получить доступ ни к одной из его ссылок SMP.
... продолжая пример, я удаляю пользователя Bill, а затем распечатываю ссылки SMP, с которыми он был связан:
Нет необходимости в каком-либо дополнительном коде для индивидуального удаления ссылки SMP отдельно и существующего кода до того, как эта функция не была изменена в любом случае.
Если есть другой способ добавить эту функцию с / без WeakMaps, не стесняйтесь комментировать.
WEAKMAP: имейте в виду, что weakMap - это все о распределении памяти и сборке мусора и связано только с ключом типа объекта в javascript, когда вы сохраняете значения в массиве пары ключ-значение, карте, наборе и т.д ... память, выделенная для всех ключей- пара значений, и эта память не будет свободной, даже если вы удалите или установите значение null для этого ключа, рассмотрите это, поскольку ключи strongmap прочно прикреплены к памяти, ниже приведен пример
let john = { name: "yusuf" };
let map = new Map();
map.set(yusuf, "xyz"); //here "yusuf" is the key and "xyz" is value
yusuf= null; // overwrite the reference
// the object previously referenced by yusuf is stored inside the array
// therefore it won't be garbage-collected
// we can get it using map.keys()
но это не относится к weakMap, здесь память будет свободной
let john = { name: "yusuf" };
let map = new WeakMap();
map.set(yusuf, "...");
yusuf= null; // overwrite the reference
// yusuf is removed from memory!
ИСПОЛЬЗУЙТЕ СЛУЧАЙ : вы будете использовать его в javascript, где хотите более эффективно управлять памятью
Если мы работаем с объектом, который «принадлежит» другому коду, может быть, даже сторонней библиотеке, и хотели бы сохранить некоторые связанные с ним данные, которые должны существовать только пока объект жив, тогда WeakMap - это именно то, что нужный.
Мы помещаем данные в WeakMap, используя объект в качестве ключа, и когда объект будет удален сборщиком мусора, эти данные также автоматически исчезнут.
weakMap.set(yusuf, "secret documents");
// if yusuf dies, secret documents will be destroyed automatically
Я взял ссылку из этой замечательной статьи: https://javascript.info/weakmap-weakset
Думаю, это очень полезно для проверки дохода от подключения в сокете приложений. В другом случае полезна "Слабая коллекция": https://javascript.info/task/recipients-read