В прошлом JavaScript был ограничен, когда речь шла о коллекциях. В то время как другие языки предлагают списки, наборы, ассоциативные карты и словари, JavaScript предлагает только массивы. Программисты JavaScript должны были придумать обходной путь для создания наборов или карт, что усложняло поддержку кода.

Теперь, с ES6, JavaScript предлагает новые встроенные классы для наборов и карт, которые значительно упрощают поддержку программирования. В этом руководстве мы подробно познакомим вас с сопоставлением и установкой вместе с примерами кода, вариантами использования и практическими упражнениями. Это руководство идеально подходит для разработчиков JavaScript, которые хотят улучшить свои навыки работы с JavaScript.

Краткий обзор этого руководства:

  • Что такое map?
  • Как использовать map
  • Что такое set?
  • Как использовать set
  • Практическое упражнение с map
  • Что изучать дальше

Что такое map?

До ES6 разработчики JavaScript использовали объекты для сопоставления ключей со значениями. Однако использование объекта в качестве карты имеет свои ограничения. Например:

  • Не существует надежного способа перебора ключей, а метод keys() преобразует поля в строки, что приводит к конфликту ключей.
  • Нет простого способа добавить новые ключи и значения

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

Проще говоря, JavaScript Map представляет собой ассоциативный набор различных ключей и значений. И ключи, и значения могут быть любым примитивом или объектом. Это важная структура данных с множеством полезных применений.

Примечание. WeakMap похоже на карту, но все ключи WeakMap являются объектами.

Чтобы создать новый Map, мы используем следующий синтаксис:

let map = new Map([iterable]);

Давайте применим это на практике на более сложном примере. Ниже у нас есть карта, которая содержит имена в качестве ключей и оценки в качестве значений.

'use strict';
//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);
scores.set('Jake', 14);
console.log(scores.size);
//END:DEFINE      
-->
5
  • Карта scores была инициализирована именами и очками. Исходными данными может быть любой итерируемый объект с парой ключей и значений.
  • Мы добавляем ключ и значение в Map, используя метод set() (строка 7)
  • Чтобы выяснить, сколько ключей в настоящее время находится на карте, мы используем свойство size (строка 9).

Примечание. Map.has(key) выше вернет логическое значение, указывающее, находится ли элемент, связанный с указанным ключом, на карте.

Как пользоваться картой

Как только мы узнаем, как создавать карты с помощью JavaScript, мы можем многое с ними делать.

Итерация по картам

Во-первых, давайте узнаем об итерации через карты. Мы можем использовать 3 метода:

  • map.keys(): возвращает итерацию для ключей
  • map.entries(): возвращает итерацию для записей [key, value]
  • map.values(): возвращает итерацию для значений

Мы можем перебирать коллекцию ключей и значений с помощью метода entries(), который возвращает итерируемый объект, поэтому мы можем использовать расширенный for loop вместе с деструктурированием.

Например, ниже мы извлекаем имя и оценку для каждой пары ключ-значение:

'use strict';
//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);
scores.set('Jake', 14);
//END:DEFINE                                                                   
for(const [name, score] of scores.entries()) {
  console.log(`${name} : ${score}`);
}
-->
Sara : 12
Bob : 11
Jill : 15
Bruce : 14
Jake : 14

Мы также можем использовать метод forEach, который является внутренним итератором.

'use strict';
//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);
scores.set('Jake', 14);
//END:DEFINE                                                                   
scores.forEach((score, name) => console.log(`${name} : ${score}`));
-->
Sara : 12
Bob : 11
Jill : 15
Bruce : 14
Jake : 14

Первый параметр, который получает функция, — это значение ключа, которое отображается как второй параметр. Тот же метод forEach() можно использовать для перебора только значений:

'use strict';
//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);
scores.set('Jake', 14);
//END:DEFINE                                                                   
scores.forEach(score => console.log(score));
-->
12
11
15
14
14

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

Инициализировать карту с итерируемым объектом

Вы также можете передать итерируемый объект конструктору Map():

let userRoles = new Map([
    [sarah, 'admin'],
    [bob, 'editor'],
    [jill, 'subscriber']
]);

Получить элемент с карты по ключу

Мы можем получить элементы с карты по ключу с помощью метода get():

Но если вы передадите ключ, которого нет на этой карте, он вернет значение undefined.

userRoles.get(sarah); // admin

Но если вы передадите ключ, которого нет на этой карте, он вернет значение undefined.

let foo = {name: 'Foo'};
userRoles.get(foo); //undefined

Получить количество элементов на карте

Мы можем использовать свойство size, чтобы получить количество элементов на наших картах.

console.log(userRoles.size); // 3

Преобразование ключей карты или значений в массив

Иногда вам может понадобиться работать с массивом вместо итерируемого объекта. Мы можем использовать оператор распространения для преобразования ключей для каждого элемента в новый массив.

var keys = [...userRoles.keys()];
console.log(keys);

Этот фрагмент кода преобразует значения элементов в массив:

var roles = [...userRoles.values()];
console.log(roles);

Другие важные методы Map

  • clear(): удаляет элементы из объекта карты.
  • map.set(key, value): сохраняет значение по ключу
  • delete(key): удаляет определенный элемент (как указано в ключе)
  • set(key, value): устанавливает значение для ключа и возвращает объект карты. Можно связать с другими методами.
  • forEach(callback[, thisArg]): вызывает обратный вызов для каждой пары ключ-значение в порядке вставки. Параметр thisArg является необязательным и устанавливает значение this для каждого обратного вызова.
  • has(key): возвращает true, если значение, связанное с ключом, существует, иначе false.
  • keys(): возвращает новый итератор с ключами для элементов в порядке вставки.
  • values(): возвращает новый объект итератора со значениями для каждого элемента в порядке вставки.
  • map.size: возвращает текущее количество элементов

Что такое set?

Set — еще одна новая коллекция, представленная ES6. Класс JavaScript Array может работать с упорядоченными коллекциями данных, но не так хорошо с неупорядоченными коллекциями или когда значения, хранящиеся в коллекции, уникальны. Вот почему JavaScript представил Set.

set — это уникальная коллекция примитивов и объектов, дубликаты не допускаются. Мы можем либо создать пустой набор и добавить объекты, либо мы можем инициализировать набор содержимым итерируемого объекта (например, массива).

Давайте рассмотрим это на примере. Ниже у нас есть набор имен с пятью значениями. Одно из значений не входит в набор из-за дублирования.

'use strict';
//START:CREATE
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
//END:CREATE
//START:SIZE
console.log(names.size);
//END:SIZE
-->
4

Мы можем добавить элементы в существующий набор, как показано ниже:

names.add('Matt');

Метод add() возвращает текущий Set, что полезно для цепных операций, таких как дополнительные вызовы add() или других методов Set:

names.add('Kate')
  .add('Kara');

Как использовать set

Как только мы поймем, как создавать наборы, с ними будет легко работать. Во-первых, давайте посмотрим на встроенные функции для наборов:

  • has(): чтобы проверить, есть ли в наборе определенный элемент.
  • clear(): очистить существующий набор или удалить существующий элемент с помощью метода delete().
  • keys(): чтобы получить все значения из набора
  • entries(): для перебора набора с использованием расширенного цикла for, как показано ниже:
'use strict';
//START:CREATE
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
//END:CREATE
//START:ADD
names.add('Mike');
//END:ADD
//START:ADD2
names.add('Kate')
  .add('Kara');
//END:ADD2
console.log(names.has('Brad'));                                         
console.log(names.entries());
console.log(names.keys());
console.log(names.values());
//START:ITERATE1
for(const name of names) {
  console.log(name);
}
//END:ITERATE1
-->
false
[Set Iterator] { 'Jack', 'Jill', 'Jake', 'Sara', 'Mike', 'Kate', 'Kara' }
[Set Iterator] { 'Jack', 'Jill', 'Jake', 'Sara', 'Mike', 'Kate', 'Kara' }
[Set Iterator] { 'Jack', 'Jill', 'Jake', 'Sara', 'Mike', 'Kate', 'Kara' }
Jack
Jill
Jake
Sara
Mike
Kate
Kara

filter/map с наборами

Набор еще не предлагает такие методы, как filter() и map(), но мы можем создать массив из набора и использовать методы функционального стиля для этого нового массива.

Например, ниже мы используем методы filter(), map() и forEach(), чтобы выбрать только имена, начинающиеся с J, а затем преобразовать их в верхний регистр.

'use strict';
//START:CREATE
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
//END:CREATE
//START:ADD
names.add('Mike');
//END:ADD
//START:ADD2
names.add('Kate')
  .add('Kara');
//END:ADD2

//START:FILTER
[...names].filter(name => name.startsWith('J'))
  .map(name => name.toUpperCase())
  .forEach(name => console.log(name));
//END:FILTER
-->
JACK
JILL
JAKE

Получить размер набора

Используйте свойство size объекта Set, чтобы вернуть его размер.

let size = chars.size;
console.log(size);//  3

Удалить элементы из набора

Чтобы удалить элемент из набора, используйте метод delete().

chars.delete('f');
console.log(chars); // Set {"a", "b", "c", "d", "e"}

А чтобы удалить все элементы набора, используйте метод clear():

chars.clear();
console.log(chars); // Set{}

Вызов функции обратного вызова для каждого элемента

Чтобы вызвать обратный вызов для каждого элемента вашего набора, используйте метод forEach().

roles.forEach(role => console.log(role.toUpperCase()));

Другие важные методы Set

  • new Set(iterable): создает набор.
  • set.add(value): добавляет заданное значение и возвращает набор
  • set.has(value): возвращает true, если значение существует в наборе, иначе возвращает false.
  • set.clear(): удалить все из набора

Практическое упражнение с map

Чтобы закрепить ваши знания, давайте выполним практическое упражнение с картой в JavaScript. Используйте Map, чтобы получить желаемый результат, как показано ниже. При создании объекта createTodo() он должен возвращать элемент карты.

Решение этой задачи приведено ниже. Сначала попробуйте сами.

'use strict';
const createTodo = function() {
  const todo = new Map();
  return todo;
}; 
const completedCount = function(map) {
  return;
};
const todo = createTodo(); //Returns a Map

**Решение:

var TestResult = function() {
    this.succeeded = false;
    this.reason = "";
    this.input = "";
    this.expected_output = "";
    this.actual_output = "";
}
var executeTests = function(){
  var inputs = [0, 1, 2, 5];
  var expected_outputs = [0, 1, 4, 25];
  var results = [];
  for (var i = 0; i < inputs.length; i++) {
    let result = new TestResult();  
    result.input = 'square(' + inputs[i] + ')';
    result.expected_output = String(expected_outputs[i]);
    // Call your Challenge function here.
    var actual_output = square(inputs[i]);
    result.actual_output = String(actual_output);
    if (actual_output === expected_outputs[i]) {
      result.succeeded = true;
      result.reason = "Succeeded"
    } else {
      result.succeeded = false;
      result.reason = "Incorrect Output"
    }
    results.push(result);
  }
  return results;
}

Разбивка решения

Начните с создания элемента карты. Объект Map todo создается в строке 4 с использованием встроенного класса. Вы можете видеть, что объект карты todo вызывает Map.get() с разными ключами, чтобы получить их значения. Это означает, что нам нужно добавить все ключи и значения.

Мы добавляем новый элемент в todo с ключами и соответствующими значениями. В строках 5-7 мы добавляем новые элементы, задавая значения для ключей.

Для completedCount() мы определяем новую функцию с параметром объекта карты. Функция вернет количество выполненных задач. Итак, по сути, мы фильтруем все значения элементов в объекте карты, чтобы получить элементы со значением, равным done (см. строку 14).

В строке 15 свойство length используется для получения количества специальных элементов.

Что изучать дальше

Map и set — ценные дополнения к JavaScript, они сделают ваш код чище и проще в обслуживании. Теперь, когда у вас есть четкое представление о карте и наборе, вы готовы заняться другими функциями, добавленными в ES6 и более поздних версиях.

Вот некоторые концепции, которые следует рассмотреть для модернизации вашего JavaScript:

  • Async и Await (обещания)
  • Метапрограммирование
  • Литералы объектов
  • Стрелочные функции

Чтобы быстро освоить функции JavaScript, ознакомьтесь с курсом Educative Открывая заново JavaScript: ES6, ES7 и ES8.. Этот курс охватывает современные функции JavaScript, чтобы сделать ваш код элегантным, лаконичным, выразительным и менее подвержен ошибкам. Вы начнете с изучения основных функций современного JavaScript, а во второй половине вы углубитесь в сложные функции, такие как деструктурирование, литералы, наследование, модули, промисы и метапрограммирование.

К концу этого курса вы сможете добавлять новые функции с минимальными усилиями и писать код более эффективно!

Удачного обучения!

Продолжить чтение о JavaScript на Educative

Начать обсуждение

Что вы надеетесь узнать о JavaScript дальше? Была ли эта статья полезна? Дайте нам знать в комментариях ниже!