Скидка до 50% и курс по ИИ в подарок 3 дня 07 :05 :13 Выбрать курс
Код JavaScript
#статьи

Что такое Set в JavaScript и зачем он нужен, если есть массивы

Если коротко — это коллекция, в которой нет повторов.

Иллюстрация: Polina Vari для Skillbox Media

Представьте, что вы собираете список email‑адресов для рассылки: одна часть пришла из формы на сайте, другая — из лендинга вебинара, а третья — из чат-бота. Если просто объединить все массивы, один и тот же адрес может встретиться несколько раз. Чтобы избежать дублей, можно вручную проверять каждый адрес перед добавлением — или сложить значения в Set.

Set — встроенная структура данных JavaScript, которая напоминает массив, но хранит только уникальные значения. Из статьи вы узнаете, как создавать сеты, какие методы у них есть и как их применять в различных задачах.

Содержание


Знакомимся с синтаксисом и создаём первый объект Set

Чтобы создать Set в JavaScript, вам достаточно написать следующую строку:

const mySet = new Set();

Если сразу вывести его в консоль, мы увидим пустой Set:

console.log(mySet); // Set(0) {}

В Chrome и некоторых других браузерах вывод может выглядеть иначе — например так: Set(0) {size: 0}. Это не ошибка, просто другой формат.

Сеты позволяют хранить любые типы данных: числа, строки, логические значения, объекты, массивы и даже другие множества Set. Кстати, слово «множество» пришло из математики: так называют коллекцию, в которой каждый элемент встречается только один раз. Например, у множества {1, 1, 2, 2, 3} после удаления всех повторов останется только {1, 2, 3}.

Для примера создадим Set с несколькими элементами:

const numbers = new Set([10, 20, 30, 20, 40]);
console.log(numbers); // Set(4) { 10, 20, 30, 40 }

Обратите внимание на число 20: в массиве оно встречалось дважды, а в Set сохранилось только один раз. Это происходит потому, что при добавлении элементов JavaScript сравнивает значения по алгоритму SameValueZero. Он похож на строгое сравнение ===, но с несколькими важными исключениями.

SameValueZero считает NaN равным NaN и не различает +0 и -0 — это два варианта нуля, которые иногда появляются при вычислениях с числами с плавающей точкой. Всё остальное сравнивается так же, как в ===: примитивы — по значению, а объекты и массивы — только по ссылке.

Разбираем основные методы и свойство size

У Set есть несколько встроенных методов и одно свойство. Прежде чем к ним перейти, создадим небольшой Set с фруктами и будем на нём тренироваться:

const fruits = new Set(['apple', 'banana', 'orange']);

add(value) — этот метод добавляет новое значение в Set. Однако если такой элемент уже есть в наборе, ничего не изменится. И поскольку add() возвращает сам Set, вы можете объединить несколько добавлений в цепочку:

fruits.add('grape'); // Добавляем новый фрукт
fruits.add('apple'); // Дубликат: apple уже есть в Set, поэтому ничего не изменится

console.log(fruits); // Set(4) { 'apple', 'banana', 'orange', 'grape' }

// А так можно добавлять элементы цепочкой
fruits.add('kiwi').add('mango'); // Set(6) { 'apple', 'banana', 'orange', 'grape', 'kiwi', 'mango' }

has(value) — проверяет, хранится ли элемент в Set, и возвращает булево значение. Вы увидите true, если элемент найден, или false, если нет:

console.log(fruits.has('banana')); // true
console.log(fruits.has('pear'));   // false

size — свойство, которое показывает сколько элементов хранится в Set:

console.log(fruits.size); // 6

delete(value) — удаляет значение из Set и возвращает true, если всё получилось. Или вы получите false, если элемента не было в наборе:

console.log(fruits.delete('banana')); // true
console.log(fruits.delete('pear'));  // false — такого элемента нет
console.log(fruits.size);            // 5

clear() — полностью очищает Set и оставляет набор пустым:

fruits.clear();
console.log(fruits); // Set(0) {}

Получаем элементы Set при переборе

Перебирать элементы Set можно несколькими способами — всё зависит от задачи. В большинстве случаев удобнее всего начать с цикла for...of:

const mySet = new Set([1, 2, 3]);

for (let value of mySet) {
    console.log(value); // 1 2 3
}

Если вам нужно вывести или преобразовать каждый элемент, то удобно использовать метод массива forEach():

const colors = new Set(['red', 'green', 'blue']);

colors.forEach((color) => {
    console.log(color); // red green blue
});

forEach() в Set вызывает колбэк с двумя первыми аргументами, но в обоих случаях передаёт одно и то же значение. Это не ошибка: у Set нет ключей и индексов, есть только значения. Такой формат разработчики языка оставили специально для совместимости с похожей коллекцией Map. Только в Map колбэк получает value и key, а в Set роль «ключа» условно играет само значение. Звучит запутанно, поэтому проще объяснить на примере:

colors.forEach((value, alsoValue) => {
    // Здесь value и alsoValue — одно и то же значение
    console.log(`${alsoValue}: ${value}`);
});
/* 
    red: red
    green: green
    blue: blue
*/

У Set также есть три метода-итератора: values(), keys() и entries(). Все они возвращают не массивы, а объекты типа SetIterator — это значит, что обратиться к элементу по индексу не получится: set.values()[0] вернёт undefined. Для перебора нужно создать цикл или преобразовать итератор в массив с помощью Array.from().

Однако для большинства задач с Set эти методы вам не понадобятся — достаточно for...of или forEach(). В основном они существуют для совместимости с Map, где у каждого из них есть свой смысл. Но если интересно, вот как они работают с Set:

const letters = new Set(['a', 'b', 'c']);

letters.values();  // SetIterator {'a', 'b', 'c'} — значения
letters.keys();    // SetIterator {'a', 'b', 'c'} — то же самое, ключей у Set нет
letters.entries(); // SetIterator {['a', 'a'], ['b', 'b'], ['c', 'c']} -- пары [значение, значение]

Проверяем уникальность объектов

До этого мы рассматривали примеры с примитивными типами данных — числами и строками. Теперь разберёмся, как Set ведёт себя с объектами.

Создадим два объекта с одинаковым содержимым и добавим их в Set:

// Два объекта с одинаковыми свойствами
const obj1 = { a: 1 };
const obj2 = { a: 1 };

// Добавляем оба в Set
const set = new Set([obj1, obj2]);

console.log(set.size); // 2

Хотя объекты кажутся одинаковыми, Set воспринимает их как разные и добавляет оба. Дело в том, что Set сравнивает не содержимое объектов, а ссылки на них. Давайте преобразуем Set в массив и попробуем сравнить элементы через ===:

const array = Array.from(set);
console.log(array[0] === array[1]); // false — объекты разные

Преобразуем Set в массив и обратно

Ранее мы превращали Set в массив через Array.from(). Это нормально, но чаще используют оператор расширения ... — он короче и читается проще:

const set = new Set([1, 2, 3]);

// Способ 1: Array.from()
const array1 = Array.from(set);

// Способ 2: оператор расширения (...)
const array2 = [...set];

console.log(array1); // [1, 2, 3]
console.log(array2); // [1, 2, 3]

А вот обратное преобразование из массива в Set делается через конструктор. При этом все дубли удаляются автоматически:

const array = [1, 2, 2, 3];
const set = new Set(array);

console.log(set); // Set(3) { 1, 2, 3 }

Хотя Set и массив легко преобразуются друг в друга, это не означает, что они взаимозаменяемы. Set удобен, когда важна уникальность: нужно удалить дубли, проверить наличие элемента или сравнить несколько списков. Массив лучше подходит, когда важен порядок элементов или допустимы повторы.

ХарактеристикаSetArray
Уникальность элементовДаНет
Порядок добавленияСохраняетсяСохраняется
Доступ по индексуНетЕсть
Добавить элементadd()push()
Проверить наличиеhas() — быстрееincludes()
Размерsizelength
ПереборforEach, for…offor, forEach, map и другие

Типичные ошибки при работе с Set в JavaScript

Set отлично справляется с удалением дублей — но только если данные однородны и сравниваются по значению. Разберём два случая, когда Set недостаточно.

Set не удаляет одинаковые объекты

Представим, вы получаете карточки товаров через API маркетплейса и среди них есть полные дубли. Кажется, что Set справится — но на самом деле это не так:

const products = [
  { title: 'Монитор', brand: 'XYZ', price: 9199 },
  { title: 'Монитор', brand: 'XYZ', price: 9199 }
];

const unique = new Set(products);
console.log(unique.size); // 2 — оба объекта остались

Это происходит по той же причине, что мы разбирали раньше: Set сравнивает объекты по ссылке. Поэтому лучше использовать Map и JSON.stringify:

const unique = Array.from(
  new Map(products.map(item => [JSON.stringify(item), item])).values()
);

console.log(unique.length); // 1
console.log(unique); // [ { title: 'Монитор', brand: 'XYZ', price: 9199 } ]

Разберём по шагам:

  • JSON.stringify — превращает объект в строку, чтобы можно было сравнивать её по содержимому.
  • .map() — создаёт из массива ключи и значения, где значения — это сами объекты item, а ключи — строковые представления объектов.
  • .values() — извлекает из Map только уникальные значения.
  • new Map() — сохраняет только уникальные ключи, удаляя дубликаты.
  • Array.from() — получаем массив уникальных значений (объектов).

Проще говоря, мы превращаем объекты в строки, сравниваем их и с помощью Map удаляем дубликаты. Set в данном случае не справляется.

Set не поможет, если данные не приведены к единому виду

Допустим, вам нужно очистить список email-адресов от дублей — но строки содержат лишние пробелы и буквы в разном регистре:

const rawEmails = [
  'test@domain.com',
  'Test@domain.com ',
  ' test@domain.com',
  'test@domain.com'
];

const unique = Array.from(new Set(rawEmails));
console.log(unique);
/* [
  'test@domain.com',
  'Test@domain.com ',
  ' test@domain.com'
] */

Как видите, Set здесь не помог: он сравнивает строки строго, поэтому воспринимает все три варианта как разные значения. Поэтому нужно сначала привести данные к единому виду и только потом применять Set:

// Убираем пробелы и приводим к нижнему регистру
const normalized = rawEmails.map(email => email.trim().toLowerCase());

// Теперь удаляем дубли
const uniqueEmails = Array.from(new Set(normalized));
console.log(uniqueEmails); // ['test@domain.com']

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

Больше интересного про код — в нашем телеграм-канале.  Подписывайтесь!


Курс с трудоустройством: «Профессия Фронтенд-разработчик + ИИ» Узнать о курсе
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована