Map & WeakMap

29.06.2018
Смотрите видео
на Youtube

Типы коллекций Map и WeakMap появились в ECMAScript 2015. Оба этих типа коллекций позволяют хранить записи вида «ключ:значение». Звучит знакомо, старый добрый тип Object также хранит пары «ключ:значение». Так в чём же фишка? Зачем нужны ещё две коллекция? На самом деле, у каждой из них есть ряд существенных особенностей. Давайте сначала разберёмся с Map!

Итак, создать новый map можно с помощью конструктора Map():


const data = new Map();

Для добавления или изменения значения по ключу используется метод set(), первый параметр — ключ, а второй — значение:


data.set('user', { name: 'Test' });

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

Мы можем добавить элемент с числовым ключом, null, undefined, NaN тоже могут быть ключами.


data.set(1, 1);
data.set(null, 2);
data.set(undefined, 3);
data.set(NaN, 4);

Даже объект или функция может быть ключом:


const obj = { test: 1 };
data.set(obj, 7);

const func = function() {};
data.set(func, 8);

Метод set() можно вызывать по цепочке:


data.set(1, ‘a’)
    .set(2, ‘b’)
    .set(3, ‘c’);

А ещё можно добавить элементы в Map прямо в конструкторе, он принимает в качестве параметра вот такой массив массивов:


const user = new Map([
    ['login', 'neo'],
    ['name', 'Thomas Anderson']
]);

Получить значение по ключу можно с помощью метода get():


console.log(data.get(func));
console.log(data.get(null));

Метод has() позволяет проверить, присутствует ли в мэпе элемент с заданным ключом, метод delete() удаляет элемент по ключу, а метод clear() очищает весь мэп.


data.has('user');
data.delete('user');
data.clear();

А ещё у Map есть свойство size, которое позволяет получить количество элементов в мэпе:


console.log(data.size);
console.log(user.size);

Ещё одно важное отличие Map от объекта заключается в том, что Map является итерируемой структурой данных, а значит может использоваться в цикле for...of, с ним можно использовать деструктуризацию, spread-оператор, Array.from() и другие языковые конструкции, работающие с итераторами.

Например, мы можем вывести все элементы мэпа с помощью цикла for...of:


for (let [key, value] of user) {
    console.log(`${key}: ${value}`);
}

Стоит отметить, что Map сохраняет первоначальный порядок элементов (т.е. тот порядок, в котором мы добавляли элементы в Map), тогда как объект этого не гарантирует.

С Map можно использовать деструктуризацию:


const [login, name] = user;
console.log(login);
console.log(name);

Или превратить Map в массив:


console.log(Array.from(user));

Остальные языковые конструкции на основе итераторов тоже прекрасно работают с мэпом.

Когда же стоит использовать Map, а когда обычный объект? Map стоит использовать в следующих случаях:

  • Если ключи являются динамическими, т.е. становятся известны только в момент выполнения.
  • Если ключ может быть не только строкой.
  • Если пары ключ:значение часто добавляются или удаляются.
  • И если нужно ли перебирать пары.

Коллекция WeakMap очень похожа на Map, но есть несколько принципиальных отличий:

  • Ключом WeakMap может быть только объект
  • У WeakMap нет свойства size
  • У WeakMap нет метода clear()
  • WeakMap не является итерируемым, а значит нельзя перебрать его значения с помощью for...of или forEach
  • И самое главное: если поместить данные в WeakMap, а объект сделать ключом, то если удалится объект-ключ, то данные тоже будут автоматически удалены из памяти.

В остальном API WeakMap точно такой же как у Map.

Аксель Раушмайер предлагает следующие юзкейсы для WeakMap:

  • Первый — это кеширование вычисляемых значений. Здесь на самом деле очень удобно использовать WeakMap: если нам нужно какое-либо значение на основе других полей объекта, мы просто сохраняем его в WeakMap, в качестве ключа используем этот самый объект, и когда оно нам повторно понадобится, берём его из кеша. Удаляется объект, значение больше не нужно и память освободится автоматически.
  • Второй юзкейс: Управление обработчиками событий. Обработчики события для объекта сохраняются в WeakMap, а ключом является сам объект. Если удаляется объект, удаляются и обработчики событий для этого объекта.
  • И третий вариант использования: хранение в WeakMap приватных данных. В этом случае приватные данные для объекта хранятся отдельно в WeakMap, а ключом является сам объект.

Примеры этих юзкейсов вы можете посмотреть в книге Exploring ES6.