WeakMap
Базовая поддержка Широко доступна
Эта функция хорошо зарекомендовала себя и работает на многих устройствах и в различных версиях браузеров. Она доступна во всех браузерах с июля 2015 года.
WeakMap — это коллекция пар ключ/значение, где ключами должны быть объекты или незарегистрированные символы, а значениями — любые произвольные типы JavaScript, и которая не создает сильных ссылок на свои ключи. Это означает, что наличие объекта в качестве ключа в WeakMap не предотвращает сборку мусора этого объекта. Как только объект, использованный в качестве ключа, будет собран, соответствующие ему значения в любом WeakMap также станут кандидатами на сборку мусора — при условии, что на них нет других сильных ссылок. Единственный примитивный тип, который может быть использован в качестве ключа WeakMap, — это символ, а точнее, незарегистрированные символы, поскольку гарантируется, что незарегистрированные символы уникальны и не могут быть повторно созданы.
WeakMap позволяет связывать данные с объектами таким образом, что это не препятствует сбору мусора объектов-ключей, даже если значения ссылаются на ключи. Однако WeakMap не позволяет наблюдать за активностью своих ключей, поэтому он не допускает перечисления; если бы WeakMap предоставлял какой-либо метод для получения списка своих ключей, этот список зависел бы от состояния сборки мусора, что приводило бы к недетерминизму. Если вам нужен список ключей, следует использовать Map, а не WeakMap.
Вы можете узнать больше о WeakMap в разделе объект WeakMap руководства Коллекции с ключами.
Описание
Ключи WeakMap должны быть сборщиками мусора. Большинство примитивных типов данных могут быть созданы произвольно и не имеют срока жизни, поэтому они не могут использоваться в качестве ключей. Объекты и незарегистрированные символы могут использоваться в качестве ключей, поскольку они поддаются сборке мусора.
Равенство ключей
Как и обычные Map, равенство значений основано на алгоритме SameValueZero, который аналогичен оператору ===, поскольку WeakMap могут хранить только объектные и символьные ключи. Это означает, что для объектных ключей равенство основано на идентичности объекта. Они сравниваются по ссылке, а не по значению.
Зачем нужен WeakMap?
API карты (map) *мог бы* быть реализован в JavaScript с помощью двух массивов (один для ключей, другой для значений), общих для четырех методов API. Установка элементов на этой карте включала бы добавление ключа и значения в конец каждого из этих массивов одновременно. В результате индексы ключа и значения соответствовали бы обоим массивам. Получение значений из карты включало бы перебор всех ключей для поиска совпадения, а затем использование индекса этого совпадения для получения соответствующего значения из массива значений.
Такая реализация имела бы два основных неудобства:
- Первое — это
O(n)установка и поиск (где n — количество ключей в карте), поскольку обе операции должны перебирать список ключей для поиска соответствующего значения. - Второе неудобство — утечка памяти, поскольку массивы гарантируют, что ссылки на каждый ключ и каждое значение сохраняются неопределенно долго. Эти ссылки предотвращают сбор мусора ключей, даже если на объект нет других ссылок. Это также предотвращает сбор мусора соответствующих значений.
Напротив, в WeakMap объект-ключ создает сильную ссылку на свое содержимое до тех пор, пока ключ не будет собран сборщиком мусора, а затем слабую. Таким образом, WeakMap:
- не предотвращает сборку мусора, которая в конечном итоге удаляет ссылки на объект-ключ
- позволяет сборщику мусора удалять значения, если на объекты-ключи нет ссылок откуда-либо, кроме
WeakMap
WeakMap может быть особенно полезной конструкцией при сопоставлении ключей с информацией о ключе, которая ценна *только в том случае*, если ключ не был собран сборщиком мусора.
Но поскольку WeakMap не позволяет наблюдать за активностью своих ключей, ее ключи не перечисляются. Нет метода для получения списка ключей. Если бы он существовал, список зависел бы от состояния сборки мусора, что приводило бы к недетерминизму. Если вам нужен список ключей, следует использовать Map.
Конструктор
-
WeakMap() - Создает новый объект
WeakMap.
Свойства экземпляра
Эти свойства определены на WeakMap.prototype и совместно используются всеми экземплярами WeakMap.
-
WeakMap.prototype.constructor - Конструкторская функция, которая создала экземпляр объекта. Для экземпляров
WeakMapначальным значением является конструкторWeakMap. -
WeakMap.prototype[Symbol.toStringTag] - Начальным значением свойства
[Symbol.toStringTag]является строка"WeakMap". Это свойство используется вObject.prototype.toString().
Методы экземпляра
-
WeakMap.prototype.delete() - Удаляет запись, указанную ключом, из этого
WeakMap. -
WeakMap.prototype.get() - Возвращает значение, соответствующее ключу в этом
WeakMap, илиundefined, если такого нет. -
WeakMap.prototype.getOrInsert() - Возвращает значение, соответствующее указанному ключу в этом
WeakMap. Если ключ отсутствует, вставляет новую запись с ключом и заданным значением по умолчанию и возвращает вставленное значение. -
WeakMap.prototype.getOrInsertComputed() - Возвращает значение, соответствующее указанному ключу в этом
WeakMap. Если ключ отсутствует, вставляет новую запись с ключом и значением по умолчанию, вычисленным из предоставленного обратного вызова, и возвращает вставленное значение. -
WeakMap.prototype.has() - Возвращает булево значение, указывающее, существует ли запись с указанным ключом в этом
WeakMap. -
WeakMap.prototype.set() - Добавляет новую запись с указанным ключом и значением в этот
WeakMapили обновляет существующую запись, если ключ уже существует.
Примеры
Использование WeakMap
const wm1 = new WeakMap();
const wm2 = new WeakMap();
const wm3 = new WeakMap();
const o1 = {};
const o2 = () => {};
const o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // a value can be anything, including an object or a function
wm2.set(o2, undefined);
wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps!
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, because that is the set value
wm2.get(o3); // undefined, because there is no key for o3 on wm2
wm1.has(o2); // true
wm2.has(o2); // true (even if the value itself is 'undefined')
wm2.has(o3); // false
wm3.set(o1, 37);
wm3.get(o1); // 37
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
Реализация класса, подобного WeakMap, с методом .clear()
class ClearableWeakMap {
#wm;
constructor(init) {
this.#wm = new WeakMap(init);
}
clear() {
this.#wm = new WeakMap();
}
delete(k) {
return this.#wm.delete(k);
}
get(k) {
return this.#wm.get(k);
}
has(k) {
return this.#wm.has(k);
}
set(k, v) {
this.#wm.set(k, v);
return this;
}
}
Эмуляция приватных членов
Разработчики могут использовать WeakMap для связывания приватных данных с объектом, что дает следующие преимущества:
- По сравнению с
Map, WeakMap не хранит сильных ссылок на объект, используемый в качестве ключа, поэтому метаданные имеют тот же срок жизни, что и сам объект, избегая утечек памяти. - По сравнению с использованием неперечисляемых и/или
Symbolсвойств, WeakMap является внешним по отношению к объекту, и нет способа для пользовательского кода получить метаданные с помощью рефлексивных методов, таких какObject.getOwnPropertySymbols. - По сравнению с замыканием, один и тот же WeakMap может быть повторно использован для всех экземпляров, созданных из конструктора, что делает его более эффективным по памяти, и позволяет различным экземплярам одного и того же класса читать приватные члены друг друга.
let Thing;
{
const privateScope = new WeakMap();
let counter = 0;
Thing = function () {
this.someProperty = "foo";
privateScope.set(this, {
hidden: ++counter,
});
};
Thing.prototype.showPublic = function () {
return this.someProperty;
};
Thing.prototype.showPrivate = function () {
return privateScope.get(this).hidden;
};
}
console.log(typeof privateScope);
// "undefined"
const thing = new Thing();
console.log(thing);
// Thing {someProperty: "foo"}
thing.showPublic();
// "foo"
thing.showPrivate();
// 1
Это примерно эквивалентно следующему, используя приватные поля:
class Thing {
static #counter = 0;
#hidden;
constructor() {
this.someProperty = "foo";
this.#hidden = ++Thing.#counter;
}
showPublic() {
return this.someProperty;
}
showPrivate() {
return this.#hidden;
}
}
const thing = new Thing();
console.log(thing);
// Thing {someProperty: "foo"}
thing.showPublic();
// "foo"
thing.showPrivate();
// 1
Связывание метаданных
WeakMap может использоваться для связывания метаданных с объектом, не влияя на срок жизни самого объекта. Это очень похоже на пример с приватными членами, поскольку приватные члены также моделируются как внешние метаданные, которые не участвуют в прототипном наследовании.
Этот вариант использования может быть расширен на уже созданные объекты. Например, в вебе мы можем захотеть связать дополнительные данные с DOM-элементом, к которым DOM-элемент может получить доступ позже. Распространенный подход — прикрепить данные как свойство:
const buttons = document.querySelectorAll(".button");
buttons.forEach((button) => {
button.clicked = false;
button.addEventListener("click", () => {
button.clicked = true;
const currentButtons = [...document.querySelectorAll(".button")];
if (currentButtons.every((button) => button.clicked)) {
console.log("All buttons have been clicked!");
}
});
});
Этот подход работает, но имеет несколько недостатков:
- Свойство
clickedявляется перечисляемым, поэтому оно будет отображаться вObject.keys(button), циклахfor...inи т. д. Этого можно избежать, используяObject.defineProperty(), но это делает код более многословным. - Свойство
clickedявляется обычным строковым свойством, поэтому его можно получить и перезаписать другим кодом. Этого можно избежать, используя ключSymbol, но ключ все равно будет доступен черезObject.getOwnPropertySymbols().
Использование WeakMap решает эти проблемы:
const buttons = document.querySelectorAll(".button");
const clicked = new WeakMap();
buttons.forEach((button) => {
clicked.set(button, false);
button.addEventListener("click", () => {
clicked.set(button, true);
const currentButtons = [...document.querySelectorAll(".button")];
if (currentButtons.every((button) => clicked.get(button))) {
console.log("All buttons have been clicked!");
}
});
});
Здесь только код, имеющий доступ к clicked, знает о состоянии нажатия каждой кнопки, и внешний код не может изменить состояния. Кроме того, если какая-либо из кнопок будет удалена из DOM, связанные с ней метаданные будут автоматически собраны сборщиком мусора.
Кэширование
Вы можете связать объекты, переданные в функцию, с результатом выполнения этой функции, так что если один и тот же объект будет передан снова, кэшированный результат может быть возвращен без повторного выполнения функции. Это полезно, если функция является чистой (то есть она не изменяет внешние объекты и не вызывает других наблюдаемых побочных эффектов).
const cache = new WeakMap();
function handleObjectValues(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = Object.values(obj).map(heavyComputation);
cache.set(obj, result);
return result;
}
Это работает только в том случае, если входные данные функции являются объектом. Более того, даже если входные данные больше никогда не будут переданы, результат все равно останется в кэше навсегда, пока ключ (входные данные) жив. Более эффективный способ — использовать Map в сочетании с объектами WeakRef, что позволяет связывать входное значение любого типа с соответствующим (потенциально большим) результатом вычислений. См. пример WeakRefs и FinalizationRegistry для более подробной информации.
Спецификации
Совместимость с браузерами
| Настольные | Мобильные | Сервер | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox for Android | Opera Android | Safari on iOS | Samsung Internet | WebView Android | WebView on iOS | Bun | Deno | Node.js | |
WeakMap |
36 |
12 |
6 |
23 |
8 |
36 |
6 |
24 |
8 |
3.0 |
37 |
8 |
1.0.0 |
1.0 |
0.12.0 |
WeakMap |
36 |
12 |
6 |
23 |
8 |
36 |
6 |
24 |
8 |
3.0 |
37 |
8 |
1.0.0 |
1.0 |
0.12.0 |
delete |
36 |
12 |
6До Firefox 38 этот метод выдавалTypeError, когда параметр key не был объектом. Это исправлено в версиях 38 и более поздних, возвращая false в соответствии со стандартом ES2015. |
23 |
8 |
36 |
6До Firefox for Android 38 этот метод выдавалTypeError, когда параметр key не был объектом. Это исправлено в версиях 38 и более поздних, возвращая false в соответствии со стандартом ES2015. |
24 |
8 |
3.0 |
37 |
8 |
1.0.0 |
1.0 |
0.12.0 |
get |
36 |
12 |
6До Firefox 38 этот метод выдавалTypeError, когда параметр key не был объектом. Однако спецификация ES2015 предусматривает возвращение undefined. Кроме того, WeakMap.prototype.get принимал необязательный второй аргумент в качестве резервного значения, которого нет в стандарте. Оба нестандартных поведения удалены в версии 38 и более поздних. |
23 |
8 |
36 |
6До Firefox for Android 38 этот метод выдавалTypeError, когда параметр key не был объектом. Однако спецификация ES2015 предусматривает возвращение undefined. Кроме того, WeakMap.prototype.get принимал необязательный второй аргумент в качестве резервного значения, которого нет в стандарте. Оба нестандартных поведения удалены в версии 38 и более поздних. |
24 |
8 |
3.0 |
37 |
8 |
1.0.0 |
1.0 |
0.12.0 |
getOrInsert |
145 |
145 |
144 |
129 |
26.2 |
145 |
144 |
96 |
26.2 |
Нет |
145 |
26.2 |
1.2.20 |
2.6.7 |
26.0.0 |
getOrInsertComputed |
145 |
145 |
144 |
129 |
26.2 |
145 |
144 |
96 |
26.2 |
Нет |
145 |
26.2 |
1.2.20 |
2.6.7 |
26.0.0 |
has |
36 |
12 |
6До Firefox 38 этот метод выдавалTypeError, когда параметр key не был объектом. Это исправлено в версиях 38 и более поздних, возвращая false в соответствии со стандартом ES2015. |
23 |
8 |
36 |
6До Firefox for Android 38 этот метод выдавалTypeError, когда параметр key не был объектом. Это исправлено в версиях 38 и более поздних, возвращая false в соответствии со стандартом ES2015. |
24 |
8 |
3.0 |
37 |
8 |
1.0.0 |
1.0 |
0.12.0 |
set |
36 |
12 |
6До Firefox 38 этот метод выдавалTypeError, когда параметр key не был объектом. Это исправлено в версиях 38 и более поздних, возвращая false в соответствии со стандартом ES2015. |
23 |
8 |
36 |
6До Firefox for Android 38 этот метод выдавалTypeError, когда параметр key не был объектом. Это исправлено в версиях 38 и более поздних, возвращая false в соответствии со стандартом ES2015. |
24 |
8 |
3.0 |
37 |
8 |
1.0.0 |
1.0 |
0.12.0 |
symbol_as_keys |
109 |
109 |
146 |
95 |
16.4 |
109 |
146 |
74 |
16.4 |
21.0 |
109 |
16.4 |
1.0.0 |
1.28 |
20.1.0 |
См. также
© 2005–2025 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap