WeakMap¶
WeakMap — это встроенный объект в языке программирования JavaScript, реализующий ассоциативный массив (коллекцию «ключ-значение»), в котором ключами могут быть только объекты (или неиспользуемые символы в некоторых версиях), а значениями — произвольные типы данных. Основная особенность WeakMap заключается в том, что он хранит «слабые» ссылки на ключи-объекты, что позволяет сборщику мусора автоматически удалять пары «ключ-значение», если на объект-ключ больше нет других ссылок в программе. Это делает WeakMap полезным для хранения дополнительных, приватных или временных данных, связанных с объектами, без риска утечки памяти.
¶История и стандартизация
WeakMap был впервые представлен в спецификации ECMAScript 2015 (ES6) как часть набора новых коллекций, наряду с Map, Set и WeakSet. Идея «слабых» ссылок существовала в других языках программирования (например, в Java — WeakHashMap, в C# — WeakReference), но в JavaScript была реализована с учётом особенностей сборки мусора и отсутствия прямого управления памятью. Введение WeakMap решило проблему утечек памяти, возникающих при использовании обычных объектов или Map для хранения данных, связанных с DOM-элементами или другими временными объектами. Стандарт ECMAScript 2021 (ES12) расширил WeakMap, разрешив использовать в качестве ключей символы (Symbol), но только те, которые зарегистрированы в глобальном реестре символов (well-known symbols). В 2023 году предложение «Symbols as WeakMap keys» (Symbols.asWeakMapKeys) было включено в черновик ECMAScript, что позволило использовать любые символы в качестве ключей.
¶Основные характеристики
¶Ключи и значения
- Ключи: только объекты (включая функции и массивы) или символы (в современных реализациях). Примитивные типы (числа, строки, булевы значения, null, undefined) не допускаются.
- Значения: любые типы данных, включая примитивы, объекты, функции, а также другие WeakMap или Map.
¶Слабые ссылки
WeakMap хранит «слабые» ссылки на ключи-объекты. Это означает, что наличие пары в WeakMap не препятствует сборщику мусора удалить объект-ключ, если на него нет других «сильных» ссылок из программы. Как только объект-ключ удаляется сборщиком мусора, соответствующая пара «ключ-значение» автоматически удаляется из WeakMap. Это поведение недетерминировано — точное время удаления зависит от реализации сборщика мусора в конкретном движке JavaScript.
¶Отсутствие итерации
WeakMap не предоставляет методов для итерации по своим элементам (например, forEach, keys(), values(), entries()), а также не имеет свойства size. Это связано с тем, что содержимое WeakMap может быть изменено сборщиком мусора в любой момент, что сделало бы итерацию ненадёжной. Для проверки наличия ключа используются методы has() и get().
¶Методы WeakMap
WeakMap имеет четыре метода, аналогичных методам Map:
set(key, value)— добавляет пару «ключ-значение» в WeakMap. Если ключ уже существует, обновляет значение. Возвращает сам WeakMap (для цепочечного вызова).get(key)— возвращает значение, связанное с ключом, илиundefined, если ключ отсутствует.has(key)— возвращаетtrue, если ключ присутствует в WeakMap, иначеfalse.delete(key)— удаляет пару по ключу. Возвращаетtrue, если удаление прошло успешно, илиfalse, если ключ не найден.
Пример использования: ``javascript const wm = new WeakMap(); const obj = {}; wm.set(obj, 'some value'); console.log(wm.get(obj)); // 'some value' console.log(wm.has(obj)); // true wm.delete(obj); console.log(wm.has(obj)); // false ``
¶Применение
¶Хранение приватных данных
WeakMap часто используется для хранения приватных свойств объектов, которые не должны быть доступны извне. Поскольку ключом является сам объект, а значение — приватные данные, эти данные не могут быть получены без ссылки на объект. Это позволяет реализовать паттерн «приватные поля» до появления нативной поддержки приватных полей в классах (ES2020).
Пример: ``javascript const privateData = new WeakMap(); class User { constructor(name) { privateData.set(this, { name }); } getName() { return privateData.get(this).name; } } ``
¶Ассоциация данных с DOM-элементами
В веб-разработке WeakMap часто применяется для связывания дополнительных данных с DOM-элементами без создания циклических ссылок, которые могли бы привести к утечкам памяти. Когда элемент удаляется из DOM, сборщик мусора может освободить память, и WeakMap автоматически очистит связанные данные.
Пример: ``javascript const elementData = new WeakMap(); const button = document.querySelector('button'); elementData.set(button, { clicks: 0 }); button.addEventListener('click', () => { const data = elementData.get(button); data.clicks++; }); ``
¶Кэширование результатов функций
WeakMap может использоваться для кэширования результатов вызовов функций, где ключом является аргумент-объект. Если объект больше не используется, кэш автоматически очищается.
Пример: ``javascript const cache = new WeakMap(); function computeExpensive(obj) { if (cache.has(obj)) { return cache.get(obj); } const result = expensiveCalculation(obj); cache.set(obj, result); return result; } ``
¶Управление памятью в библиотеках и фреймворках
Многие современные JavaScript-библиотеки (например, Vue.js, React) используют WeakMap для хранения внутреннего состояния компонентов, связанного с DOM-элементами или виртуальными узлами, чтобы избежать утечек памяти при удалении компонентов.
¶Ограничения и особенности
- Нельзя проверить размер: отсутствие свойства
sizeи методов итерации делает WeakMap непригодным для случаев, когда нужно перебрать все элементы или узнать количество записей. - Недетерминированное удаление: нельзя полагаться на то, что пара будет удалена сразу после потери последней сильной ссылки на ключ. Удаление происходит только во время циклов сборки мусора.
- Только объекты и символы в качестве ключей: это ограничение делает WeakMap менее универсальным, чем Map, но необходимым для реализации слабых ссылок.
- Совместимость: WeakMap поддерживается во всех современных браузерах и средах выполнения JavaScript (Node.js, Deno). В старых средах (например, Internet Explorer 11) поддержка может отсутствовать или быть неполной.
¶Сравнение с Map
| Характеристика | Map | WeakMap |
|---|---|---|
| Типы ключей | Любые (примитивы, объекты) | Только объекты и символы |
| Слабые ссылки | Нет (сильные ссылки) | Да (слабые ссылки на ключи) |
| Итерация | Да (forEach, keys(), values(), entries()) | Нет |
Свойство size | Да | Нет |
| Утечки памяти | Возможны при хранении ссылок на временные объекты | Минимизированы |
| Производительность | Немного быстрее для итераций | Немного быстрее для операций с одним ключом |
¶Интересные факты
- В спецификации ECMAScript 2021 (ES12) появилась возможность использовать в качестве ключей WeakMap «известные символы» (well-known symbols), такие как
Symbol.toStringTagилиSymbol.iterator, но только в том случае, если они зарегистрированы в глобальном реестре. - В 2023 году было принято предложение «Symbols as WeakMap keys», которое разрешило использовать любые символы в качестве ключей, что расширило возможности WeakMap.
- WeakMap не является единственной реализацией слабых ссылок в JavaScript. Существует также
WeakSet, который хранит только объекты (слабые ссылки) без значений, а также глобальный объектWeakRef(ES2021), позволяющий создавать слабые ссылки на произвольные объекты.
¶Источники
- Спецификация ECMAScript 2015 (ES6) — раздел «WeakMap Objects»
- Спецификация ECMAScript 2021 (ES12) — раздел «WeakMap»
- MDN Web Docs: WeakMap — JavaScript
- Статья «WeakMap and WeakSet» на JavaScript.info
- Предложение «Symbols as WeakMap keys» (TC39 Proposal)
