Как мы знаем из главы «Сбор мусора», движок JavaScript хранит значение в памяти, пока оно «доступно» и потенциально может быть использовано.
Например:
пусть Джон = {имя: "Джон"}; // объект доступен, john — это ссылка на него // перезаписываем ссылку Джон = ноль; // объект будет удален из памяти
Обычно свойства объекта или элементов массива или другой структуры данных считаются доступными и хранятся в памяти, пока эта структура данных находится в памяти.
Например, если мы поместим объект в массив, то пока массив жив, объект тоже будет жив, даже если на него нет других ссылок.
Так:
пусть Джон = {имя: "Джон"}; пусть массив = [Джон]; Джон = ноль; // перезаписываем ссылку // объект, на который ранее ссылался Джон, хранится внутри массива // следовательно, он не будет подвергаться сборке мусора // мы можем получить его как массив[0]
Аналогично, если мы используем объект в качестве ключа в обычном Map
, то пока существует Map
, этот объект также существует. Он занимает память и не подлежит сборке мусора.
Например:
пусть Джон = {имя: "Джон"}; пусть карта = новая карта(); map.set(Джон, "..."); Джон = ноль; // перезаписываем ссылку // Джон хранится внутри карты, // мы можем получить его, используя map.keys()
WeakMap
принципиально отличается в этом аспекте. Это не предотвращает сбор мусора ключевых объектов.
Давайте посмотрим, что это значит на примерах.
Первое различие между Map
и WeakMap
заключается в том, что ключи должны быть объектами, а не примитивными значениями:
пусть слабая карта = новая WeakMap (); пусть объект = {}; слабыйMap.set(obj, «ок»); // работает нормально (ключ объекта) // нельзя использовать строку в качестве ключа слабыйMap.set("тест", "Упс"); // Ошибка, поскольку «тест» не является объектом
Теперь, если мы используем объект в качестве ключа в нем и на этот объект нет других ссылок – он будет удален из памяти (и с карты) автоматически.
пусть Джон = {имя: "Джон"}; пусть слабая карта = новая WeakMap (); слабыйMap.set(Джон, "..."); Джон = ноль; // перезаписываем ссылку // Джон удален из памяти!
Сравните его с обычным примером Map
выше. Теперь, если john
существует только как ключ WeakMap
– он будет автоматически удален с карты (и памяти).
WeakMap
не поддерживает итерацию и keys()
, values()
, entries()
, поэтому невозможно получить из него все ключи или значения.
WeakMap
имеет только следующие методы:
weakMap.set(key, value)
weakMap.get(key)
weakMap.delete(key)
weakMap.has(key)
Почему такое ограничение? Это по техническим причинам. Если объект потерял все другие ссылки (например, john
в приведенном выше коде), то он автоматически подлежит сборке мусора. Но технически точно не указано , когда именно произойдет очистка .
Это решает движок JavaScript. Он может выбрать выполнение очистки памяти немедленно или подождать и выполнить очистку позже, когда произойдет дальнейшее удаление. Таким образом, технически текущее количество элементов WeakMap
неизвестно. Возможно, двигатель очистил его или нет, или сделал это частично. По этой причине методы, которые обращаются ко всем ключам/значениям, не поддерживаются.
Итак, где нам нужна такая структура данных?
Основная область применения WeakMap
— дополнительное хранилище данных .
Если мы работаем с объектом, который «принадлежит» другому коду, возможно, даже сторонней библиотеке, и хотели бы сохранить некоторые связанные с ним данные, которые должны существовать только пока объект жив — тогда WeakMap
— это именно то, что нужно. нужный.
Мы помещаем данные в WeakMap
, используя объект в качестве ключа, и когда объект будет очищен от мусора, эти данные также автоматически исчезнут.
слабыйMap.set(Джон, "секретные документы"); // если Джон умрет, секретные документы будут автоматически уничтожены
Давайте посмотрим на пример.
Например, у нас есть код, который ведет подсчет посещений пользователей. Информация хранится на карте: пользовательский объект является ключом, а количество посещений — значением. Когда пользователь уходит (его объект собирает мусор), мы больше не хотим хранить количество его посещений.
Вот пример функции подсчета с Map
:
// ? visitCount.js пусть visitsCountMap = новая карта(); // карта: пользователь => количество посещений // увеличиваем количество посещений функция countUser(пользователь) { let count = visitCountMap.get(user) || 0; visitsCountMap.set(пользователь, количество + 1); }
А вот еще одна часть кода, возможно, другой файл, использующий его:
// ? main.js пусть Джон = {имя: "Джон"}; countUser (Джон); // считаем его посещения // позже Джон покидает нас Джон = ноль;
Теперь объект john
должен быть удален сборщиком мусора, но остается в памяти, поскольку он является ключом в visitsCountMap
.
Нам необходимо очищать visitsCountMap
при удалении пользователей, иначе он будет бесконечно расти в памяти. Такая очистка может стать утомительной задачей в сложной архитектуре.
Мы можем избежать этого, переключившись на WeakMap
:
// ? visitCount.js пусть visitsCountMap = новый WeakMap(); // слабая карта: пользователь => количество посещений // увеличиваем количество посещений функция countUser(пользователь) { let count = visitCountMap.get(user) || 0; visitsCountMap.set(пользователь, количество + 1); }
Теперь нам не нужно очищать visitsCountMap
. После того, как объект john
становится недоступным, любым способом, кроме как в качестве ключа WeakMap
, он удаляется из памяти вместе с информацией по этому ключу из WeakMap
.
Другой распространенный пример — кэширование. Мы можем хранить («кэшировать») результаты функции, чтобы будущие вызовы того же объекта могли повторно использовать их.
Для этого мы можем использовать Map
(не оптимальный сценарий):
// ? кэш.js пусть кэш = новая карта(); // вычисляем и запоминаем результат функция процесс(объект) { если (!cache.has(obj)) { let result = /* расчет результата для */ obj; кэш.set(объект, результат); вернуть результат; } вернуть кэш.получить(объект); } // Теперь мы используем процесс() в другом файле: // ? main.js let obj = {/* допустим, у нас есть объект */}; пусть результат1 = процесс (объект); // рассчитано // ...позже, из другого места кода... пусть result2 = процесс (объект); // запоминаем результат, взятый из кэша // ...позже, когда объект больше не понадобится: объект = ноль; оповещение(кэш.размер); // 1 (Ой! Объект все еще находится в кеше и занимает память!)
Для нескольких вызовов process(obj)
с одним и тем же объектом он вычисляет результат только в первый раз, а затем просто берет его из cache
. Обратной стороной является то, что нам нужно очистить cache
, когда объект больше не нужен.
Если мы заменим Map
на WeakMap
, то эта проблема исчезнет. Кэшированный результат будет автоматически удален из памяти после того, как объект будет очищен от мусора.
// ? кэш.js пусть кэш = новый WeakMap(); // вычисляем и запоминаем результат функция процесс(объект) { если (!cache.has(obj)) { let result = /* вычисляем результат для */ obj; кэш.set(объект, результат); вернуть результат; } вернуть кэш.получить(объект); } // ? main.js let obj = {/* какой-то объект */}; пусть результат1 = процесс (объект); пусть результат2 = процесс (объект); // ...позже, когда объект больше не понадобится: объект = ноль; // Невозможно получить кэш.размер, так как это WeakMap, // но это 0 или скоро станет 0 // Когда obj соберет мусор, кэшированные данные также будут удалены
WeakSet
ведет себя аналогично:
Он аналогичен Set
, но в WeakSet
мы можем добавлять только объекты (не примитивы).
Объект существует в наборе, пока он доступен откуда-то еще.
Как и Set
, он поддерживает add
, has
и delete
, но не size
, keys()
и никаких итераций.
Будучи «слабым», он также служит дополнительным хранилищем. Но не для произвольных данных, а для фактов «да/нет». Членство в WeakSet
может что-то означать для объекта.
Например, мы можем добавить пользователей в WeakSet
чтобы отслеживать тех, кто посетил наш сайт:
пусть visitSet = новый WeakSet(); пусть Джон = {имя: "Джон"}; let Пит = {имя: "Пит"}; пусть Мэри = {имя: "Мэри"}; посетилSet.add(Джон); // Джон посетил нас посетилSet.add(Пит); // Тогда Пит посетилSet.add(Джон); // Джон снова // у VisitSet теперь 2 пользователя // проверяем, заходил ли Джон? оповещение(visitedSet.has(Джон)); // истинный // проверяем, посещала ли Мэри? оповещение(visitedSet.has(Мэри)); // ЛОЖЬ Джон = ноль; // visitSet будет очищен автоматически
Наиболее заметным ограничением WeakMap
и WeakSet
является отсутствие итераций и невозможность получить весь текущий контент. Это может показаться неудобным, но не мешает WeakMap/WeakSet
выполнять свою основную работу — быть «дополнительным» хранилищем данных для объектов, которые хранятся/управляются в другом месте.
WeakMap
— это коллекция, подобная Map
, которая допускает только объекты в качестве ключей и удаляет их вместе со связанным значением, когда они становятся недоступными другими способами.
WeakSet
— это Set
-подобная коллекция, которая хранит только объекты и удаляет их, когда они становятся недоступными другими способами.
Их основные преимущества в том, что они имеют слабую ссылку на объекты, поэтому их легко удалить сборщиком мусора.
Это происходит за счет отсутствия поддержки clear
, size
, keys
, values
…
WeakMap
и WeakSet
используются как «вторичные» структуры данных в дополнение к «основному» объектному хранилищу. После удаления объекта из основного хранилища, если он найден только как ключ WeakMap
или в WeakSet
, он будет очищен автоматически.
важность: 5
Есть массив сообщений:
пусть сообщения = [ {текст: «Привет», от: «Джон»}, {текст: «Как дела?», от: «Джон»}, {текст: «До скорой встречи», от: «Алиса»} ];
Ваш код может получить к нему доступ, но сообщения управляются чужим кодом. Новые сообщения добавляются, старые удаляются этим кодом регулярно, и вы не знаете точных моментов, когда это происходит.
Теперь, какую структуру данных вы могли бы использовать для хранения информации о том, «прочитано» ли сообщение? Структура должна хорошо подходить, чтобы дать ответ «читалось ли?» для данного объекта сообщения.
PS Когда сообщение удаляется из messages
, оно должно исчезнуть и из вашей структуры.
PPS Нам не следует модифицировать объекты сообщений, а добавлять к ним свои свойства. Поскольку ими управляет чужой код, это может привести к плохим последствиям.
Давайте сохраним прочитанные сообщения в WeakSet
:
пусть сообщения = [ {текст: «Привет», от: «Джон»}, {текст: «Как дела?», от: «Джон»}, {текст: «До скорой встречи», от: «Алиса»} ]; пусть readMessages = новый WeakSet(); // два сообщения были прочитаны readMessages.add(сообщения[0]); readMessages.add(сообщения[1]); // readMessages имеет 2 элемента // ...прочитаем первое сообщение еще раз! readMessages.add(сообщения[0]); // readMessages все еще содержит 2 уникальных элемента // ответ: сообщение[0] было прочитано? alert("Прочитать сообщение 0: " + readMessages.has(messages[0])); // истинный сообщения.сдвиг(); // теперь readMessages имеет 1 элемент (технически память можно очистить позже)
WeakSet
позволяет хранить набор сообщений и легко проверять наличие в нем сообщения.
Он очищается автоматически. Компромисс в том, что мы не можем перебирать его, не можем напрямую получить из него «все прочитанные сообщения». Но мы можем сделать это, перебрав все сообщения и отфильтровав те, которые есть в наборе.
Другим решением может быть добавление свойства типа message.isRead=true
к сообщению после его прочтения. Поскольку объекты сообщений управляются другим кодом, это обычно не рекомендуется, но мы можем использовать символическое свойство, чтобы избежать конфликтов.
Так:
// символическое свойство известно только нашему коду пусть isRead = Символ("isRead"); сообщения[0][isRead] = правда;
Теперь сторонний код, вероятно, не увидит наше дополнительное свойство.
Хотя символы позволяют снизить вероятность возникновения проблем, с архитектурной точки зрения лучше использовать WeakSet
.
важность: 5
Есть массив сообщений, как и в предыдущей задаче. Ситуация аналогичная.
пусть сообщения = [ {текст: «Привет», от: «Джон»}, {текст: «Как дела?», от: «Джон»}, {текст: «До скорой встречи», от: «Алиса»} ];
Теперь возникает вопрос: какую структуру данных вы бы предложили для хранения информации: «когда сообщение было прочитано?».
В предыдущей задаче нам нужно было сохранить только факт «да/нет». Теперь нам нужно сохранить дату, и она должна оставаться в памяти только до тех пор, пока сообщение не будет удалено.
PS Даты можно хранить как объекты встроенного класса Date
, о которых мы поговорим позже.
Чтобы сохранить дату, мы можем использовать WeakMap
:
пусть сообщения = [ {текст: «Привет», от: «Джон»}, {текст: «Как дела?», от: «Джон»}, {текст: «До скорой встречи», от: «Алиса»} ]; пусть readMap = новый WeakMap(); readMap.set(messages[0], новая дата(2017, 1, 1)); // Объект даты, который мы изучим позже