Что такое объект Set в JavaScript и его применение в Node.js?

Ответ

Set — это встроенная в JavaScript коллекция, которая хранит уникальные значения любого типа (примитивы или ссылки на объекты). В Node.js Set часто используется для эффективной работы с уникальными данными, устранения дубликатов и проверки принадлежности.

Основные характеристики:

  • Уникальность: Каждое значение может встречаться в Set только один раз. Проверка уникальности использует алгоритм "SameValueZero" (аналогично строгому равенству ===, но где NaN считается равным NaN).
  • Порядок: Элементы перебираются в порядке их добавления.
  • Производительность: Методы add, has, delete работают в среднем за O(1), что делает Set очень эффективным для проверки наличия элемента.

Базовые операции:

const uniqueTags = new Set();

// Добавление
uniqueTags.add('javascript');
uniqueTags.add('nodejs');
uniqueTags.add('javascript'); // Не добавится, дубликат
console.log(uniqueTags); // Set(2) { 'javascript', 'nodejs' }

// Проверка наличия
console.log(uniqueTags.has('nodejs')); // true

// Удаление
uniqueTags.delete('javascript');

// Размер коллекции
console.log(uniqueTags.size); // 1

// Итерация
for (const tag of uniqueTags) {
    console.log(tag); // 'nodejs'
}
// Или преобразование в массив
const tagArray = [...uniqueTags]; // ['nodejs']

Практические примеры использования в Node.js:

  1. Удаление дубликатов из массива — самый частый случай:
    const duplicateIds = [101, 102, 101, 103, 102, 104];
    const uniqueIds = [...new Set(duplicateIds)];
    console.log(uniqueIds); // [101, 102, 103, 104]
  2. Кэширование или отслеживание уже обработанных элементов:

    // При обработке потока данных (например, из WebSocket)
    const processedMessageIds = new Set();
    
    socket.on('message', (message) => {
        if (processedMessageIds.has(message.id)) {
            return; // Уже обрабатывали, игнорируем
        }
        processedMessageIds.add(message.id);
        // ... логика обработки сообщения
    });
  3. Проверка на допустимые значения:

    const validStatuses = new Set(['pending', 'processing', 'shipped', 'delivered']);
    
    function updateOrderStatus(newStatus) {
        if (!validStatuses.has(newStatus)) {
            throw new Error(`Invalid status: ${newStatus}`);
        }
        // ... обновление статуса в БД
    }
  4. Построение отношений (объединение, пересечение, разность):

    const admins = new Set(['user1@mail.com', 'user2@mail.com']);
    const activeUsers = new Set(['user2@mail.com', 'user3@mail.com']);
    
    // Пользователи, которые являются и админами, и активными
    const activeAdmins = new Set([...admins].filter(x => activeUsers.has(x)));
    
    // Все уникальные пользователи из обоих множеств (объединение)
    const allUsers = new Set([...admins, ...activeUsers]);

Отличие от массива: Set не имеет индексов и методов типа map или filter (но его легко преобразовать в массив для этого). Его сила — в гарантированной уникальности и быстром поиске. Отличие от объекта ({}): Set специализирован на хранение коллекции значений, а не пар ключ-значение. Ключами в Set являются сами значения.

Ответ 18+ 🔞

Давай разберемся, что это за зверь такой — Set в JavaScript. Представь себе, что у тебя есть мешок, но не простой, а волшебный: ты пытаешься засунуть в него две одинаковые конфеты, а он одну просто выплевывает. Вот этот мешок — и есть Set. Коллекция, которая хранит только уникальные значения любого типа — хоть строки, хоть числа, хоть ссылки на объекты. В Node.js эта штука просто незаменима, когда нужно быстро проверить, было ли что-то уже, или отфильтровать дубликаты.

Что он из себя представляет, ёпта:

  • Уникальность — его конёк: Одно значение — один раз. Точка. Проверяет он это по алгоритму "SameValueZero", это почти как строгое равенство (===), но с одной хитрой жопой: NaN считается равным самому себе. Так что два NaN в один Set не запихнёшь — ни хуя себе, да?
  • Порядок: Элементы перебираются ровно в том порядке, в котором ты их туда закидывал. Не как в обычном объекте, где порядок ключей может быть ебушки-воробушки.
  • Скорость — просто пиздец: Методы add, has, delete работают в среднем за O(1). Это значит, что проверка, есть ли элемент в коллекции из миллиона записей, происходит почти мгновенно. Доверия ебать ноль к массивам для таких задач после этого.

Базовые операции, чтобы не быть манда с ушами:

const uniqueTags = new Set();

// Добавляем
uniqueTags.add('javascript');
uniqueTags.add('nodejs');
uniqueTags.add('javascript'); // Второй раз не добавится, дубль же!
console.log(uniqueTags); // Set(2) { 'javascript', 'nodejs' }

// Проверяем, есть ли
console.log(uniqueTags.has('nodejs')); // true

// Удаляем
uniqueTags.delete('javascript');

// Смотрим размер
console.log(uniqueTags.size); // 1

// Перебираем
for (const tag of uniqueTags) {
    console.log(tag); // 'nodejs'
}
// Или просто превращаем в массив, если надо
const tagArray = [...uniqueTags]; // ['nodejs']

Где это реально пригождается в Node.js (практика, блядь):

  1. Удаление дубликатов из массива — святое дело:
    const duplicateIds = [101, 102, 101, 103, 102, 104];
    const uniqueIds = [...new Set(duplicateIds)]; // Всё, дубли накрылись медным тазом
    console.log(uniqueIds); // [101, 102, 103, 104]
  2. Кэш или трекер обработанного говна:

    // Допустим, обрабатываешь сообщения из сокета
    const processedMessageIds = new Set();
    
    socket.on('message', (message) => {
        if (processedMessageIds.has(message.id)) {
            return; // Уже видел это, иди на хуй, сообщение
        }
        processedMessageIds.add(message.id);
        // ... дальше уже делаешь с ним что нужно
    });
  3. Валидация допустимых значений:

    const validStatuses = new Set(['pending', 'processing', 'shipped', 'delivered']);
    
    function updateOrderStatus(newStatus) {
        if (!validStatuses.has(newStatus)) {
            throw new Error(`Invalid status: ${newStatus}`); // Подсовываешь хуйню — получаешь ошибку
        }
        // ... нормально обновляешь статус в базе
    }
  4. Работа с множествами (объединение, пересечение):

    const admins = new Set(['user1@mail.com', 'user2@mail.com']);
    const activeUsers = new Set(['user2@mail.com', 'user3@mail.com']);
    
    // Кто и админ, и активный (пересечение)
    const activeAdmins = new Set([...admins].filter(x => activeUsers.has(x)));
    
    // Все уникальные юзеры с обоих списков (объединение)
    const allUsers = new Set([...admins, ...activeUsers]);

Чем он не является:

  • Это не массив. У него нет индексов, map или filter прямо из коробки. Но его всегда можно разобрать в массив оператором ... — и тогда делай с ним что хочешь.
  • Это не объект {}. Он заточен под коллекцию значений, а не пар ключ-значение. Ключ здесь — это само значение. Проще, блядь, некуда.