Перейти к содержимому
Теория · Map, Set и JSON · урок 3 из 9

Коллекция Set

В уроке про Map гардероб хранил пары «номерок — пальто». Бывает, что пара не нужна, а важно одно: был ли уже такой гость. Представьте охранника на входе со списком гостей. Каждое имя он записывает один раз, а на вопрос «Анна уже пришла?» отвечает, заглянув в список.

В массиве такой список приходится вести вручную: перед каждым push проверять includes. Встроенная коллекция Set делает эту проверку сама. Пустую коллекцию создаёт запись new Set():

JavaScript
const tags = new Set();
tags.add("js");
tags.add("css");
tags.add("js");

console.log(tags.size); // 2
console.log(tags.has("css"), tags.has("html")); // true false

Давайте разберём по шагам:

  1. new Set() создаёт пустую коллекцию tags.
  2. Два первых add записывают "js" и "css".
  3. Третий add с "js" ничего не меняет: такое значение уже есть.
  4. Поэтому size равен 2.
  5. has("css") даёт true, а has("html") — false.

Что умеет Set

Set хранит отдельные значения, без ключей, и каждое значение — только один раз. Повторный вызов add с тем же значением ничего не меняет.

У Set есть:

  • метод has отвечает, есть ли значение в коллекции;
  • метод delete удаляет значение;
  • свойство size хранит число значений.

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

Такая коллекция подходит, когда важно только одно: встречалось ли значение раньше. Например, это посещённые страницы, выданные номера или теги статьи.

Какие значения равны

Set сравнивает значения так же, как оператор ===. Число 3 и строка "3" для него разные, поэтому остаются оба. Отличие от === одно: NaN === NaN даёт false, а Set считает два NaN одним значением.

Объекты сравниваются по ссылке, и два объекта с одинаковыми свойствами — два разных значения:

JavaScript
const values = new Set([3, "3", 3, NaN, NaN]);
console.log(values.size); // 3

const points = new Set([{ x: 1 }, { x: 1 }]);
console.log(points.size); // 2

Запись new Set(arr) с массивом в скобках добавляет его элементы по очереди, как если бы для каждого вызвали add. Поэтому повторы из массива в коллекцию не попадают:

  • в values остаются 3, "3" и один NaN, всего три значения;
  • в points два разных объекта, хотя свойства у них одинаковые.

Здесь аналогия со списком гостей перестаёт работать. Охранник узнал бы двух гостей с одним именем и записал бы одного. Set с объектами смотрит не на свойства, а на сам объект: две записи { x: 1 } создают два разных объекта.

Массив без повторов

Set помнит порядок добавления, и цикл for…of проходит значения в этом порядке. Синтаксис расширения превращает Set обратно в массив. Вместе new Set и синтаксис расширения дают массив без повторов. Каждое значение стоит там, где встретилось впервые, а повторы пропадают:

JavaScript
const nums = [3, 1, 3, 2, 1];
const seen = new Set(nums);

for (const n of seen) {
    console.log(n);
}
// 3
// 1
// 2

console.log([...seen].join(" ")); // 3 1 2

Исходный массив nums при этом не меняется: new Set(nums) только читает его элементы.

Этот приём часто спрашивают на собеседовании: «Как убрать повторы из массива одной строкой?» Ответ — [...new Set(nums)].

Частая ошибка

К значению Set обратились по индексу, как к элементу массива:

JavaScript
const seen = new Set(["a", "b"]);

console.log(seen[0]); // ✗

Программа выводит undefined. У Set есть порядок, но нет индексов, и seen.length тоже даёт undefined. Set нужен, чтобы быстро проверять, есть ли в нём значение, а не чтобы брать значения по позиции. Чтобы взять значение по номеру, сначала превратите Set в массив: [...seen][0] даёт "a".

Главное

  • Set хранит каждое значение один раз: повторный add ничего не меняет.
  • has проверяет, есть ли значение, а size хранит число значений.
  • Значения сравниваются как через ===, только два NaN считаются одним значением, а объекты — по ссылке.
  • [...new Set(nums)] даёт массив без повторов в порядке первого появления.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом