Что такое Set в JavaScript и зачем он нужен, если есть массивы
Если коротко — это коллекция, в которой нет повторов.
Представьте, что вы собираете список email‑адресов для рассылки: одна часть пришла из формы на сайте, другая — из лендинга вебинара, а третья — из чат-бота. Если просто объединить все массивы, один и тот же адрес может встретиться несколько раз. Чтобы избежать дублей, можно вручную проверять каждый адрес перед добавлением — или сложить значения в Set.
Set — встроенная структура данных JavaScript, которая напоминает массив, но хранит только уникальные значения. Из статьи вы узнаете, как создавать сеты, какие методы у них есть и как их применять в различных задачах.
Содержание
- Знакомимся с синтаксисом и создаём первый объект Set
- Разбираем основные методы и свойство size
- Получаем элементы Set при переборе
- Проверяем уникальность объектов
- Преобразуем Set в массив и обратно
- Типичные ошибки при работе с Set
Знакомимся с синтаксисом и создаём первый объект 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')); // falsesize — свойство, которое показывает сколько элементов хранится в Set:
console.log(fruits.size); // 6delete(value) — удаляет значение из Set и возвращает true, если всё получилось. Или вы получите false, если элемента не было в наборе:
console.log(fruits.delete('banana')); // true
console.log(fruits.delete('pear')); // false — такого элемента нет
console.log(fruits.size); // 5clear() — полностью очищает 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 удобен, когда важна уникальность: нужно удалить дубли, проверить наличие элемента или сравнить несколько списков. Массив лучше подходит, когда важен порядок элементов или допустимы повторы.
| Характеристика | Set | Array |
|---|---|---|
| Уникальность элементов | Да | Нет |
| Порядок добавления | Сохраняется | Сохраняется |
| Доступ по индексу | Нет | Есть |
| Добавить элемент | add() | push() |
| Проверить наличие | has() — быстрее | includes() |
| Размер | size | length |
| Перебор | forEach, for…of | for, 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 не распознает их как одинаковые.
Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!
