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

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

Вот пример:

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

Соответственно, если применить объект в качестве ключа в обычной 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);

Далее мы рассмотрим 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);

Итерация и методы 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);

Таким образом, при удалении пользователей необходимо очистить 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);

Теперь нет необходимости очищать 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

В случае вызова 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

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

Сводка

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

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

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

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

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

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

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