В стандарте EcmaScript 2015, больше известном, как ES6, появился объект Map (карта), который позволяет сохранить данные в виде коллекции из пар ключ/значение, примерно такой же как и Object
. Однако Map
позволяет использовать ключи любого типа, а не только в виде строк. В каком-то смысле она похожа и на двумерный массив, и на объект, но с более сложной организацией.Для того чтобы создать коллекцию Map, нельзя использовать литерал, необходимо создать экземпляр класса Map. Например:
1 | let myMap = new Map(); |
Для того чтобы заполнить коллекцию, можно использовать метод myMap.set(key, value)
, то есть записать в ключ key
значение value
. Например:
1 2 3 | myMap.set(12, 'number'); // число в качестве ключа myMap.set("Some string", 'string'); // строка в качестве ключа myMap.set(true, 'boolean'); // логическое значение как ключ |
Можно передать в Map данные в виде пар [key, value]
сразу при создании экземпляра. Интересно, что в виде ключей в Map могут выступать и объекты, и массивы, и даже функции, что невозможно для обычных объектов, создаваемых как литералы вида let person = { name: "Mary", age: 23 }
или let lisa = new Object( {name: "Lisa", age: 15 } )
. В этом случае запись данных в коллекцию Map может быть такой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let person = { name: "Mary", age: 23 }, numArr = [4, 89, 22, 13], sumFunc = function(a, b) { return a + b }; let objMap = new Map([ [numArr, 'array'], [person, 'object'], [sumFunc, 'function'] ]); console.log(objMap); |
Получить информацию о содержимом коллекции Map или удалить/очистить информацию в ней можно с помощью таких методов:
map.get(key)
– возвращает значение по указанному в скобках ключу. Если ключkey
отсутствует, вернетundefined
.map.has(key)
– возвращаетtrue
, если ключkey
присутствует в коллекции, если его нет -false
.map.delete(key)
– удаляет элемент по переданному ключуkey
.map.clear()
– очищает коллекцию от всех элементов.map.size
– возвращает текущее количество элементов. Обратите внимание, что это свойство, а не метод. Круглые скобки не нужны.
Например, если мы захотим проверить, какое значение лежит в коллекции objMap
из кода выше по ключу numArr, то нужно будет записать:
1 | console.log(objMap.get(numArr)); // 'array' |
Если нужно проверить наличие определенного ключа, то нам поможет метод objMap.has(person)
.
Пример использования методов работы с коллекцией Map
Давайте попробуем создать несколько html-элементов с помощью коллекции Map и изображений с сайта unsplash.com, которые выведем в тело документа с помощью document.write(). На сайте unsplash.com подберем изображения фруктов, а затем добавим их в коллекцию fruitsMap
. Переберем эту коллекцию с помощью встроенного метода forEach()
для Map и при добавлении разметки запишем обработчик события onclick как атрибут тега <figure>.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const fruitsMap = new Map([ ['apple', 'https://source.unsplash.com/gDPaDDy6_WE/150x150'], ['pear', 'https://source.unsplash.com/bLPjs4Kw9JU/150x150'], ['peach', 'https://source.unsplash.com/jVYnBn3M9R0/150x150'], ['banana', 'https://source.unsplash.com/QIW4IkjxP-w/150x150'], ['orange', 'https://source.unsplash.com/9002s2VnOAY/150x150'] ]); document.write('<div class="test d-flex">'); fruitsMap.forEach( (value, key, map) =>{ console.log(value); document.write(`<figure onclick="this.remove(); fruitsMap.delete('${key}');"> <figcaption>${key}</figcaption> <img src="${value}" alt="${key}"> </figure>`); }); document.write('</div><div><button onclick="alert(fruitsMap.size)">Сколько элементов в fruitsMap?</button></div>'); |
Обратите внимание, что при удалении элемента из fruitsMap
ключ помещен в кавычки, т.к. в коллекции он имеет строковый тип данных.
Попробуйте сами подсчитать количество элементов в fruitsMap
в самом начале и при удалении картинок по щелчку левой кнопкой мыши.
Практическое использование коллекции Map для перевода слов
Рассмотрим еще один пример, который может пригодиться в практике преподавателей иностранных языков. Мы запишем в коллекцию Map 10 английских слов и их перевод, а затем выведем их для пользователя в виде перечня из слов, которые надо перевести, и полей ввода, куда нужно записать перевод. Причем сделаем подсказку пользователю в виде выпадающего списка, в котором будут представлены все варианты перевода.
Давайте посмотрим на работающий пример:
HTML-разметка примера:
1 2 3 4 5 6 7 | <form name="englishForm" id="englishForm" class="test-form"> <div class="task"></div> <p id="info" class="text-center"></p> <div class="form-block"> <input type="button" id="checkBtn" name="checkBtn" value="Проверить"> </div> </form> |
Стили вы можете написать сами, но в скрипте предполагается наличие одного класса, содержащего css-свойство display: flex
(.d-flex) и вложенных в него двух div-элементов с классом .my-col, содержащим правило flex: 0 0 50%
для создания 2-х одинаковых по ширине столбцов с помощью модели Flexbox.
Код JavaScript
В JS-коде необходимо:
- Создать коллекцию Map с 10 английскими и русскими словами. Мы назовем ее
wordsMap
. - Получить доступ к элементу
<div class="task"></div>
, который будет заполнен словами изwordsMap
. - Сформировать выпадающий список с подсказками в виде
<datalist id="rusTranslate">
, куда будут помещаться только русские слова, т.е.wordsMap.values()
. - Преобразовать
wordsMap
в массив с помощью статического метода массивов Array.from() и отсортировать его по алфавиту, т.к. нам нельзя выдавать подсказки к английским словам в том же порядке, в каком они идут вwordsMap
. - Перебрать все ключи и значения коллекции
wordsMap
и вывести их в виде разметки в 2 столбца. При этом мы запишем правильное значение перевода в атрибутdata-answer
, чтобы потом сравнить его значение с темvalue
, которое пользователь введет в поле ввода. - Получить доступ ко все сформированным на основе прохода по элементам wordsMap
<div class="my-col russian">
, чтобы обработать значения каждого из размещенного в них input-а при клике на кнопке сid="checkBtn"
. - Сравнить при клике
value
поля ввода и значение атрибутаdata-answer
. Если слово переведено верно, то ставим+
возле английского слова, добавляя к заложенному при формировании строки разметки элементу<span>
класс .plus. В случае неверного ответа - ставим-
перед английским словом с помощью класса .minus. - Определить количество правильных и неправильных ответов по количеству соответствующих классов
.plus
и.minus
. - Вывести результат в абзаце перед кнопкой с
id="resultInfo"
и заодно проверить окончания в слове "ответов", которое зависит от цифры перед ним: 1 ответ, 2 ответа, 5 ответов.
Как можно заметить из списка, при создании кода нужно учесть целый ряд моментов, хотя пример совсем не кажется сложным.
Вы можете найти комментарии всех действий в коде ниже. Помимо обработки значений коллекции Map, вам понадобится понимание того, как осуществляется выбор разных элементов на html-странице и обработка событий.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | const wordsMap = new Map([ ["friendship", "дружба"], ["ship", "корабль"], ["to sleep", "спать"], ["beautiful", "прекрасный"], ["city", "город"], ["favorite", "любимый"], ["frighten", "пугать"], ["delight", "восторг"], ["bench", "скамейка"], ["enough", "достаточно"], ]); let task = document.querySelector('.task'), str = ''; let list = `<datalist id="rusTranslate">`; // создаем выпадающий список из подсказок let wordsArr = Array.from(wordsMap.values());// преобразуем в массив значения коллекции в виде русских слов wordsArr.sort(); //сортируем массив по алфавиту for (let rus of wordsArr) { list += `<option value="${rus}">${rus}</option>`; // добавляем элементы списка } list += '</datalist>'; str += list; for (let [key, value] of wordsMap) { //формируем строку, содержащую разметку с английскими словами и полями для ввода русских аналогов str += `<div class="d-flex"> <div class="my-col english"><span></span>${key}</div> <div class="my-col russian"> <input type="text" name="${key}" data-answer="${value}" list="rusTranslate" value=""> </div> </div>`; } task.innerHTML = str; //заполняем <div class="task"></div> содержимым переменной str let russian = document.getElementsByClassName('russian'); //получаем все <div class="my-col russian">, в которых есть поля ввода checkBtn.addEventListener('click', function() { //обрабатываем клик на кнопке "Проверить" for (let one of russian) { let input = one.firstElementChild; // находим поле ввода для каждого слова //если слово переведено верно/неверно, то ставим +/- перед английским словом с помощью соответствующего класса one.previousElementSibling.firstElementChild.className = input.value === input.dataset.answer ? 'plus' : 'minus'; } // определяем количество правильных и неправильных ответов по количеству соответствующих классов let rightAnswers = document.querySelectorAll('.plus').length, wrongAnswers = document.querySelectorAll('.minus').length; //выводим результат в абзаце перед кнопкой с id="resultInfo" resultInfo.textContent = 'У вас '+ rightAnswers +' правильных ответ'+(rightAnswers==1 ? '': rightAnswers>1 && rightAnswers<5? 'а':'ов')+' и ' + wrongAnswers + ' неверных ответ'+(wrongAnswers==1 ? '' : wrongAnswers>1 && wrongAnswers<5? 'а':'ов'); }) |
Методы Map.keys(), Map.values(), Map.entries()
Для перебора данных внутри Map можно использовать несколько методов, которые дают доступ к ключам, значениям или обоим данным.
В примере ниже мы используем объект Map для того, чтобы назначить стилевые свойства для обзаца, и методы keys()
и values()
- для того, чтобы вывести, какие данные мы использовали. Для перебора данных с помощью обоих методов мы будем использовать циклы for ... of, т.к. объекты типа Map являются итерируемыми, т.е. все их данные в виде пар "ключ" - "значение" имеют индексы.
See the Pen Map with styles by Elen (@ambassador) on CodePen.
See the Pen Object Map from paragraph style by Elen (@ambassador) on CodePen.
Отличия объектов (Object) и коллекций Map
Работа с коллекциями Map временами напоминает работу с объектами. Подход может быть похожим, но между объектами и коллекциями Map есть ряд отличий:
- В объектах ключами могут быть только строки. В Map ключами могут быть строки, числа, объекты и даже функции.
- Коллекции Map являются итерируемыми, и могут перебираться в циклах
for..of
или методомforEach()
. Перебрать таким образом свойства объекта не удасться, так как строковые значения не являются итерируемыми. - В Map можно получить размер коллекции с помощью свойства size (аналогично length для массивов). В объектах мы не можем получить их размер, т.к. такого свойства у них нет. В объектах можно посчитать количество свойств, используя методы
keys()
илиvalues()
. - Объекты очень просто перевести в JSON-формат с помощью метода
JSON.stringify()
или, напротив, из JSON-формата перевести данные в объект методомJSON.parse()
. Для коллекции Map так просто это сделать не получится.
В заключение
Для браузеров на основе Chrome коллекции Map могут содержать 16 миллионов записей, а объекты могут содержать только 11 миллионов. Так что использовать Map можно и для очень больших наборов данных.