- Set, Map, WeakSet и WeakMap
- Map
- Итерация
- Set
- WeakMap и WeakSet
- Итого
- JavaScript Maps
- Map Methods
- How to Create a Map
- new Map()
- Example
- Map.set()
- Example
- Example
- Map.get()
- Example
- Map.size
- Example
- Map.delete()
- Example
- Map.clear()
- Example
- Map.has()
- Example
- Try This:
- Maps are Objects
- Example
- Example
- JavaScript Objects vs Maps
- Differences between JavaScript Objects and Maps:
- Map.forEach()
- Example
- Map.entries()
- Example
- Map.keys()
- Example
- Map.values()
- Example
- Example
- Objects as Keys
- Example
- Example
- Browser Support
Set, Map, WeakSet и WeakMap
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/map-set.
В ES-2015 появились новые типы коллекций в JavaScript: Set , Map , WeakSet и WeakMap .
Map
Map – коллекция для хранения записей вида ключ:значение .
В отличие от объектов, в которых ключами могут быть только строки, в Map ключом может быть произвольное значение, например:
'use strict'; let map = new Map(); map.set('1', 'str1'); // ключ-строка map.set(1, 'num1'); // число map.set(true, 'bool1'); // булевое значение // в обычном объекте это было бы одно и то же, // map сохраняет тип ключа alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3
Как видно из примера выше, для сохранения и чтения значений используются методы get и set . И ключи и значения сохраняются «как есть», без преобразований типов.
Свойство map.size хранит общее количество записей в map .
map .set('1', 'str1') .set(1, 'num1') .set(true, 'bool1');
При создании Map можно сразу инициализировать списком значений.
Объект map с тремя ключами, как и в примере выше:
let map = new Map([ ['1', 'str1'], [1, 'num1'], [true, 'bool1'] ]);
Аргументом new Map должен быть итерируемый объект (не обязательно именно массив). Везде утиная типизация, максимальная гибкость.
В качестве ключей map можно использовать и объекты:
'use strict'; let user = < name: "Вася" >; // для каждого пользователя будем хранить количество посещений let visitsCountMap = new Map(); // объект user является ключом в visitsCountMap visitsCountMap.set(user, 123); alert( visitsCountMap.get(user) ); // 123
Использование объектов в качестве ключей – как раз тот случай, когда Map сложно заменить обычными объектами Object . Ведь для обычных объектов ключ может быть только строкой.
Для проверки значений на эквивалентность используется алгоритм SameValueZero. Он аналогичен строгому равенству === , отличие – в том, что NaN считается равным NaN . Поэтому значение NaN также может быть использовано в качестве ключа.
Этот алгоритм нельзя изменять или задавать свою функцию сравнения.
Методы для удаления записей:
- map.delete(key) удаляет запись с ключом key , возвращает true , если такая запись была, иначе false .
- map.clear() – удаляет все записи, очищает map .
Для проверки существования ключа:
Итерация
Для итерации по map используется один из трёх методов:
- map.keys() – возвращает итерируемый объект для ключей,
- map.values() – возвращает итерируемый объект для значений,
- map.entries() – возвращает итерируемый объект для записей [ключ, значение] , он используется по умолчанию в for..of .
'use strict'; let recipeMap = new Map([ ['огурцов', '500 гр'], ['помидоров', '350 гр'], ['сметаны', '50 гр'] ]); // цикл по ключам for(let fruit of recipeMap.keys()) < alert(fruit); // огурцов, помидоров, сметаны >// цикл по значениям for(let amount of recipeMap.values()) < alert(amount); // 500 гр, 350 гр, 50 гр >// цикл по записям [ключ,значение] for(let entry of recipeMap) < // то же что и recipeMap.entries() alert(entry); // огурцов,500 гр , и т.д., массивы по 2 значения >
Перебор осуществляется в порядке вставки. Объекты Map гарантируют это, в отличие от обычных объектов Object .
Кроме того, у Map есть стандартный метод forEach , аналогичный встроенному в массивы:
'use strict'; let recipeMap = new Map([ ['огурцов', '500 гр'], ['помидоров', '350 гр'], ['сметаны', '50 гр'] ]); recipeMap.forEach( (value, key, map) => < alert(`$: $`); // огурцов: 500 гр, и т.д. >);
Set
Set – коллекция для хранения множества значений, причём каждое значение может встречаться лишь один раз.
Например, к нам приходят посетители, и мы хотели бы сохранять всех, кто пришёл. При этом повторные визиты не должны приводить к дубликатам, то есть каждого посетителя нужно «посчитать» ровно один раз.
Set для этого отлично подходит:
'use strict'; let set = new Set(); let vasya = ; let petya = ; let dasha = ; // посещения, некоторые пользователи заходят много раз set.add(vasya); set.add(petya); set.add(dasha); set.add(vasya); set.add(petya); // set сохраняет только уникальные значения alert( set.size ); // 3 set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
В примере выше многократные добавления одного и того же объекта в set не создают лишних копий.
Альтернатива Set – это массивы с поиском дубликата при каждом добавлении, но они гораздо хуже по производительности. Или можно использовать обычные объекты, где в качестве ключа выступает какой-нибудь уникальный идентификатор посетителя. Но это менее удобно, чем простой и наглядный Set .
- set.add(item) – добавляет в коллекцию item , возвращает set (чейнится).
- set.delete(item) – удаляет item из коллекции, возвращает true , если он там был, иначе false .
- set.has(item) – возвращает true , если item есть в коллекции, иначе false .
- set.clear() – очищает set .
Перебор Set осуществляется через forEach или for..of аналогично Map :
'use strict'; let set = new Set(["апельсины", "яблоки", "бананы"]); // то же, что: for(let value of set) set.forEach((value, valueAgain, set) => < alert(value); // апельсины, затем яблоки, затем бананы >);
Заметим, что в Set у функции в .forEach три аргумента: значение, ещё раз значение, и затем сам перебираемый объект set . При этом значение повторяется в аргументах два раза.
Так сделано для совместимости с Map , где у .forEach -функции также три аргумента. Но в Set первые два всегда совпадают и содержат очередное значение множества.
WeakMap и WeakSet
WeakSet – особый вид Set , не препятствующий сборщику мусора удалять свои элементы. То же самое – WeakMap для Map .
То есть, если некий объект присутствует только в WeakSet/WeakMap – он удаляется из памяти.
Это нужно для тех ситуаций, когда основное место для хранения и использования объектов находится где-то в другом месте кода, а здесь мы хотим хранить для них «вспомогательные» данные, существующие лишь пока жив объект.
Например, у нас есть элементы на странице или, к примеру, пользователи, и мы хотим хранить для них вспомогательную информацию, например обработчики событий или просто данные, но действительные лишь пока объект, к которому они относятся, существует.
Если поместить такие данные в WeakMap , а объект сделать ключом, то они будут автоматически удалены из памяти, когда удалится элемент.
// текущие активные пользователи let activeUsers = [ , , ]; // вспомогательная информация о них, // которая напрямую не входит в объект юзера, // и потому хранится отдельно let weakMap = new WeakMap(); weakMap.set(activeUsers[0], 1); weakMap.set(activeUsers[1], 2); weakMap.set(activeUsers[2], 3); weakMap.set('Katya', 4); //Будет ошибка TypeError: "Katya" is not a non-null object alert( weakMap.get(activeUsers[0]) ); // 1 activeUsers.splice(0, 1); // Вася более не активный пользователь // weakMap теперь содержит только 2 элемента activeUsers.splice(0, 1); // Петя более не активный пользователь // weakMap теперь содержит только 1 элемент
Таким образом, WeakMap избавляет нас от необходимости вручную удалять вспомогательные данные, когда удалён основной объект.
У WeakMap есть ряд ограничений:
- Только объекты в качестве ключей.
- Нет свойства size .
- Нельзя перебрать элементы итератором или forEach .
- Нет метода clear() .
Иными словами, WeakMap работает только на запись ( set , delete ) и чтение ( get , has ) элементов по конкретному ключу, а не как полноценная коллекция. Нельзя вывести всё содержимое WeakMap , нет соответствующих методов.
Это связано с тем, что содержимое WeakMap может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. В равной степени он не гарантирует и обратное. Нет какого-то конкретного момента, когда такая очистка точно произойдёт – это определяется внутренними алгоритмами сборщика и его сведениями о системе.
Поэтому содержимое WeakMap в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации WeakMap , и связано отсутствие методов, осуществляющих доступ ко всем записям.
То же самое относится и к WeakSet : можно добавлять элементы, проверять их наличие, но нельзя получить их список и даже узнать количество.
Эти ограничения могут показаться неудобными, но, по сути, они не мешают WeakMap/WeakSet выполнять свою основную задачу – быть «вторичным» хранилищем данных для объектов, актуальный список которых (и сами они) хранится в каком-то другом месте.
Итого
- Map – коллекция записей вида ключ: значение , лучше Object тем, что перебирает всегда в порядке вставки и допускает любые ключи.
- Set – коллекция уникальных элементов, также допускает любые ключи.
Основная область применения Map – ситуации, когда строковых ключей не хватает (нужно хранить соответствия для ключей-объектов), либо когда строковый ключ может быть совершенно произвольным.
К примеру, в обычном объекте Object нельзя использовать «совершенно любые» ключи. Есть встроенные методы, и уж точно есть свойство с названием __proto__ , которое зарезервировано системой. Если название ключа даётся посетителем сайта, то он может попытаться использовать такое свойство, заменить прототип, а это, при запуске JavaScript на сервере, уже может привести к серьёзным ошибкам.
- WeakMap и WeakSet – «урезанные» по функциональности варианты Map/Set , которые позволяют только «точечно» обращаться к элементам (по конкретному ключу или значению). Они не препятствуют сборке мусора, то есть, если ссылка на объект осталась только в WeakSet/WeakMap – она будет удалена.
JavaScript Maps
A Map holds key-value pairs where the keys can be any datatype.
A Map remembers the original insertion order of the keys.
A Map has a property that represents the size of the map.
Map Methods
Method | Description |
---|---|
new Map() | Creates a new Map object |
set() | Sets the value for a key in a Map |
get() | Gets the value for a key in a Map |
clear() | Removes all the elements from a Map |
delete() | Removes a Map element specified by a key |
has() | Returns true if a key exists in a Map |
forEach() | Invokes a callback for each key/value pair in a Map |
entries() | Returns an iterator object with the Map data type in javascript pairs in a Map |
keys() | Returns an iterator object with the keys in a Map |
values() | Returns an iterator object of the values in a Map |
Property | Description |
---|---|
size | Returns the number of Map elements |
How to Create a Map
You can create a JavaScript Map by:
new Map()
You can create a Map by passing an Array to the new Map() constructor:
Example
Map.set()
You can add elements to a Map with the set() method:
Example
// Create a Map
const fruits = new Map();
// Set Map Values
fruits.set(«apples», 500);
fruits.set(«bananas», 300);
fruits.set(«oranges», 200);
The set() method can also be used to change existing Map values:
Example
Map.get()
The get() method gets the value of a key in a Map:
Example
Map.size
The size property returns the number of elements in a Map:
Example
Map.delete()
The delete() method removes a Map element:
Example
Map.clear()
The clear() method removes all the elements from a Map:
Example
Map.has()
The has() method returns true if a key exists in a Map:
Example
Try This:
Maps are Objects
Example
instanceof Map returns true:
Example
JavaScript Objects vs Maps
Differences between JavaScript Objects and Maps:
Object | Map |
---|---|
Not directly iterable | Directly iterable |
Do not have a size property | Have a size property |
Keys must be Strings (or Symbols) | Keys can be any datatype |
Keys are not well ordered | Keys are ordered by insertion |
Have default keys | Do not have default keys |
Map.forEach()
The forEach() method invokes a callback for each key/value pair in a Map:
Example
// List all entries
let text = «»;
fruits.forEach (function(value, key) text += key + ‘ = ‘ + value;
>)
Map.entries()
The entries() method returns an iterator object with the Map data type in javascript in a Map:
Example
Map.keys()
The keys() method returns an iterator object with the keys in a Map:
Example
Map.values()
The values() method returns an iterator object with the values in a Map:
Example
You can use the values() method to sum the values in a Map:
Example
Objects as Keys
Being able to use objects as keys is an important Map feature.
Example
// Create a Map
const fruits = new Map();
// Add new Elements to the Map
fruits.set(apples, 500);
fruits.set(bananas, 300);
fruits.set(oranges, 200);
Remember: The key is an object (apples), not a string («apples»):
Example
Browser Support
JavaScript Maps are supported in all browsers, except Internet Explorer: