Map и WeakMap

Map

Map – это коллекция ключ/значение ([key, value]) как и Object. Но основное отличие в том, что Map позволяет использовать ключи любого типа.

Методы и свойства

Использование NaN в качестве ключей Map

Чтобы сравнивать ключи, объект Map использует алгоритм SameValueZero. Это почти такое же сравнение, что и ===, с той лишь разницей, что NaN считается равным NaN. Так что NaN также может использоваться в качестве ключа.

WeakMap

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

Синтаксис

weakMap = new WeakMap([iterable])

Параметры

iterable
Необязательный. Может быть массивом или любым другим итерируемым объектом, элементы которого являются парами ключ-значение (массивы из двух элементов). Каждая пара ключ-значение будет добавлена во вновь созданный экземпляр WeakMap. Null обрабатывается как undefined.

WeakMap не поддерживает перебор и методы keys(), values(), entries(), так что нет способа взять все ключи или значения из неё.

В WeakMap присутствуют только следующие методы:

Пример использования объекта WeakMap

const wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
const o1 = {},
    o2 = function(){},
    o3 = window,
    o4 = [1, 2, 3];

wm1.set(o1, 37);
wm1.set(o2, 'azerty');
wm2.set(o1, o2);        // значением может быть что угодно, включая объект или функцию
wm2.set(o3, undefined);
wm2.set(wm1, wm2);      // ключами и значениями могут быть объекты. Даже WeakMap-ами

wm1.get(o2); // 'azerty'
wm2.get(o2); // undefined, нет значения для o2 в wm2
wm2.get(o3); // undefined, это установленное значение

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (даже если значение равно 'undefined')


wm3.set(o1, 37);
wm3.get(o1);     // 37

wm1.has(o1);     // true
wm1.delete(o1);
wm1.has(o1);     // false

Вторым отличием от Map - cлабые (weaks) ссылки.

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

let jsCode = {code: "js"},
    tsCode = {code: "ts"};
let js = {lang: "JavaScript"},
    ts = {lang: "TypeScript"};
const weakMap = new WeakMap([[jsCode, js], [tsCode, ts]]);
 
jsCode = null;
 
console.log(weakMap);   // WeakMap {{code: "js"} => {lang: "JavaScript"}, {code: "ts"} => {lang: "TypeScript"}}
console.log("Некоторая работа");
const timerId = setTimeout(function(){
    console.log(weakMap);   // WeakMap {{code: "ts"} => {lang: "TypeScript"}}
    clearTimeout(timerId);
}, 10000);

В данном случае сначала объект WeakMap хранит ссылки на два элемента с ключами jsCode и tsCode. Далее для переменной jsCode устанавливается значение null.

jsCode = null;

Это приведет к тому, что спустя некоторое время начальное значение этой переменной будет удалено сборщиком мусора JavaScript.

Причем если сразу после этого мы посмотрим на содержимое weakMap, то увидим, что объект с ключом jsCode в нем еще присутствует. Однако спустя некоторое время ссылка будет удалена из weakMap. Для эмуляции прошествия времени здесь используется функция setTimeout, которая выводит на консоль содержимое weakMap через 10 секунд (конкретный период времени, через который сборщик мусора удалит значение, может отличаться)

Теперь сравним с тем, что произойдет, если вместо WeakMap использовать Map:

let jsCode = {code: "js"},
    tsCode = {code: "ts"};
let js = {lang: "JavaScript"},
    ts = {lang: "TypeScript"};
const map = new Map([[jsCode, js], [tsCode, ts]]);
 
jsCode = null;
 
console.log(map);   // Map(2) {{code: "js"} => {lang: "JavaScript"}, {code: "ts"} => {lang: "TypeScript"}}
console.log("Некоторая работа");
const timerId = setTimeout(function(){
    console.log(map);   // Map(2) {{code: "js"} => {lang: "JavaScript"}, {code: "ts"} => {lang: "TypeScript"}}
    clearTimeout(timerId);
}, 10000);

В случае с Map даже спустя некоторое время мы увидим, что в объекте Map до сих пор присутствует объект, для которого было установлено значение null