JavaScript WeakMap и WeakSet

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

Вот пример:

Javascript remove object from memory
let js = {
name: "Javascript"
};
// object can be accessed, js is the reference to it
// rewrite the reference
js = null;
// the object will be deleted from memory
console.log(js);
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Вот пример:

Javascript cache a function result
let js = {
name: "Javascript"
};
let array = [js];
js = null; // rewrite the reference
// js is stored in an array, so it will not garbage-collected
// it can be received as an array[0]
console.log(js);
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Соответственно, если применить объект в качестве ключа в обычной Map, он будет существовать до тех пор, пока существует карта.

Пример будет выглядеть так:

Javascript cache a function result
let js = {
name: "Javascript"
};
let map = new Map();
map.set(js, "...");
js = null; // rewriting the reference
// js is on the map,
// it can be received by using map.keys()
console.log(js);
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Далее мы рассмотрим WeakMap, который совершенно отличается и не удерживает объекты ключей от сборки мусора.

WeakMap

Основное отличие между Map и WeakMap состоит в том, что ключом WeakMap не могут быть примитивные значения. Они должны быть объектами, как в примере ниже:

Javascript WeakMap
let weakMap = new WeakMap();
let obj = {};
key1 = weakMap.set(obj, "ok"); // works fine, object key
console.log(key1);
// can't use string as key
key2 = weakMap.set("test", "Oops"); // Error because the “test” is not an object
console.log(key2);
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Итерация и методы keys(), values(), entries() не поддерживаются WeakMap.

Методы, поддерживаемые WeakMap следующие: weakMap.get(key), weakMap.set(key, value), weakMap.has(key), и weakMap.delete(key).

weakMap.delete(key)

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

let weakMap = new WeakMap();
let obj = {
  name: "test"
};
key = weakMap.set(obj, "test docs");
// if obj disappears, test docs will be automatically destroyed

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

После ухода пользователя вы намерены прекратить хранение информации о его посещениях.

Сначала давайте рассмотрим пример функции подсчета с Map. Она будет выглядеть так:

Javascript WeakMap additional data
// visitsCount.js
let visitsCountMap = new Map(); // map: book => visits count
// increase the visits count
function countBook(book) {
let count = visitsCountMap.get(book) || 0;
visitsCountMap.set(book, count + 1);
console.log(count);
}
// main.js
let js = {
name: "Javascript"
};
countBook(js); // count his visits
countBook(js); // count his visits
// later js leaves us
js = null;
console.log(js);
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Таким образом, при удалении пользователей необходимо очистить visitsCountMap. В противном случае, она будет храниться в памяти бесконечно.

Однако иногда такая очистка может стать неприятной задачей. Если вы хотите этого избежать, вы можете обратиться к WeakMap. Вот пример использования WeakMap вместо такой очистки:

Javascript WeakMap additional data
// visitsCount.js
let visitsCountMap = new WeakMap(); // weakmap: book => visits count
// increase the visits count
function countBook(book) {
let count = visitsCountMap.get(book) || 0;
visitsCountMap.set(book, count + 1);
console.log(count);
}
// main.js
let js = {
name: "Javascript"
};
countBook(js); // count his visits
countBook(js); // count his visits
// later js leaves us
js = null;
console.log(js);
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Теперь нет необходимости очищать visitsCountMap.

О кэшировании

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

Map может быть использован для хранения результатов следующим образом:

Javascript cache a function result
// cache.js
let cache = new Map();
// calculate and remember the result
function process(myObj) {
if (!cache.has(myObj)) {
let res = /* result calculations for */ myObj;
cache.set(myObj, res);
}
return cache.get(myObj);
}
// Now we use process() in another file:
// main.js
let myObj = { /* let's say we have an object */ };
let res1 = process(myObj); // calculated
// later from another place in the code
let res2 = process(myObj); // the result taken from the cache is remembered
// later when an object is no longer needed:
myObj = null;
console.log(cache.size); // 1
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

В случае вызова process(obj) с тем же объектом несколько раз, он вычислит результат только в первый раз. Позже он будет брать информацию из кэша.

Единственным недостатком кэширования является необходимость очистки кэша, как только вы больше не нуждаетесь в объекте.

Замена Map на WeakMap решит проблему. Закешированная информация будет автоматически удалена из памяти, как только объект будет собран как мусор.

Для большей точности рассмотрим пример ниже:

// cache.js
let cache = new WeakMap();
// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let res = /* calculate the result for */ obj;
    cache.set(obj, res);
  }
  return cache.get(obj);
}
 
// main.js
let obj = { /* some object */ };
let res1 = process(obj);
let res2 = process(obj);
// later, when object is no longer needed:
obj = null;
// Can not get cache.size because it is  WeakMap,
// but it is 0 or soon will be 0
//Once object gets garbage collected, the cached data will also be cleaned

WeakSet

WeakSet считается эквивалентом Set. Однако в WeakSet могут быть добавлены только объекты, а не примитивы.

Объект находится в наборе, пока он достижим в другом месте.

WeakSet также поддерживает has, add и delete. Но не поддерживает итерации или size и keys().

Он также может служить дополнительным хранилищем для данных, но не для произвольных данных. Вот как выглядит пример добавления языков в WeakSet для отслеживания тех, на которых создан сайт:

Javascript WeakSet
let createdSet = new WeakSet();
let html = {
name: "Html"
};
let php = {
name: "php"
};
let js = {
name: "Javascript"
};
createdSet.add(html); // Html created us
createdSet.add(js); // Then Javascript
createdSet.add(html); // Html again
// createdSet has 2 languages now
// check if html created?
console.log(createdSet.has(html)); // true
// check if php created?
console.log(createdSet.has(php)); // false
html = null;
// createdSet will be cleaned automatically
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

У WeakMap и WeakSet есть серьезное ограничение: они не поддерживают итерации. Также нет возможности получить все текущее содержимое.

Сводка

В этой главе мы рассмотрели WeakMap и WeakSet.

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

WeakSet считается коллекцией, похожей на Set, которая хранит только объекты и удаляет их, когда они становятся недостижимыми.

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

Время Викторины: Проверьте Свои Навыки!

отовы проверить свои знания? Погрузитесь в наши интерактивные викторины для более глубокого понимания и веселого способа закрепить знания.

Считаете ли это полезным?