Перейти к содержанию
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы
Свернуть
exlends
Категории
  1. Главная
  2. Категории
  3. Языки программирования
  4. JavaScript
  5. Filter + indexOf vs Set: как быстро нормализовать товары из API

Filter + indexOf vs Set: как быстро нормализовать товары из API

Запланировано Прикреплена Закрыта Перенесена JavaScript
set vs filterпроизводительностьapi
1 Сообщения 1 Постеры 3 Просмотры
  • Сначала старые
  • Сначала новые
  • По количеству голосов
Ответить
  • Ответить, создав новую тему
Авторизуйтесь, чтобы ответить
Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
  • hannadevH Не в сети
    hannadevH Не в сети
    hannadev
    написал отредактировано
    #1

    Когда с API летит список товаров с дублями по ID, нужно их убрать — и желательно без лагов на фронте. Классический подход filter + indexOf работает, но сожрёт производительность на больших объёмах. Set выглядит проще, но с объектами по ссылкам он бесполезен. Разбираемся, почему один способ быстрее другого в 10 раз, и как не наступить на грабли.

    Почему filter + indexOf — это костыль на больших данных

    Все начинается просто: нужно отфильтровать массив и оставить только первое вхождение каждого элемента. Код выглядит честно и понятно:

    const filtered = products.filter((item, index) => 
      products.indexOf(item.id) === index
    );
    

    Смотрится красиво, но под капотом происходит O(n²) ужас. Для каждого элемента в цикле filter мы снова прошиваем весь массив через indexOf, ища его первое вхождение. На 15 тысячах товаров это 225 миллионов операций сравнения. Плюс indexOf работает с ссылками, поэтому два объекта с одинаковым ID будут считаться разными — нужна дополнительная логика через JSON.stringify, что ещё больше замедляет.

    Результат: фильтр по категории в админке лагает, юзеры видят висячий UI, всем грустно. Когда allowedIds из стора — это 3 тысячи штук, а основной список 15 тысяч, каждое изменение фильтра превращается в пытку.

    Проблема в том, что indexOf вызывается для каждого элемента, и каждый раз он ползёт по всему массиву заново. Никакой оптимизации, никакой памяти о том, что ты уже ищешь. Просто тупо O(n) × O(n) = боль.

    Set: волшебство с примитивами, разочарование с объектами

    Set — это структура данных, которая хранит только уникальные значения и даёт доступ за O(1). Звучит как решение всех проблем:

    const uniqueIds = new Set(bigArray.map(id => id));
    const filtered = products.filter(p => uniqueIds.has(p.id));
    

    Это O(n) вместо O(n²), и разница ощущается сразу — мгновенно вместо лагов. С примитивами (числа, строки) Set работает идеально: добавляешь элемент, Set сам проверяет, нет ли его уже, и хранит только уникальные.

    Но есть нюанс, который многие пропускают. Set проверяет равенство по значению для примитивов, но для объектов — по ссылке. Два объекта с одинаковым ID будут считаться разными, потому что это разные экземпляры в памяти. Поэтому просто так set(objects) не сработает — нужно класть в Set ID, а не сами объекты.

    Ещё один момент: если хранить строки длинных ID (например, UUID), могут быть хэш-коллизии при нехватке памяти, но это редкая проблема в реальных приложениях. Главное — Set безопасен и быстр, если использовать его правильно.

    Правильный паттерн: Set для индексирования, Map для объектов

    Для нормализации товаров из API нужен гибридный подход. Если нужно отфильтровать объекты по ID, используй Set только для ID:

    const allowedIds = new Set(categories.map(c => c.id));
    const filtered = products.filter(p => allowedIds.has(p.id));
    

    Это читаемо, быстро, и нет магии. Фильтр остаётся простым, все условия явные.

    Если же нужно полностью нормализовать список и убрать дублирующиеся объекты, Map по ID — золотая середина:

    const normalized = new Map(products.map(p => [p.id, p]));
    const result = Array.from(normalized.values());
    

    Мап сам отсекает дубли при set: если два объекта с одинаковым ID, второй перезапишет первый. О(n) на создание, O(1) на доступ. Если нужна логика типа «оставить последний по дате» или «выбрать по какому-то критерию», можно добавить условие в момент set:

    products.forEach(p => {
      const existing = normalized.get(p.id);
      if (!existing || p.updatedAt > existing.updatedAt) {
        normalized.set(p.id, p);
      }
    });
    

    Сравнение на реальных данных

    Подход Сложность Скорость на 15k товаров С объектами Читаемость
    filter + indexOf O(n²) Лаги, ~500ms+ Требует JSON.stringify Средняя
    Set (только ID) O(n) Мгновенно, ~5ms Работает, но нужен отдельный фильтр Высокая
    Map по ID O(n) Мгновенно, ~8ms Работает идеально, дубли отсекаются Высокая
    filter + Set O(n) Мгновенно, ~3ms Работает, но хак Хорошая

    Видишь разницу? С Set и Map мы уходим с O(n²) на O(n) — это не просто ускорение, это спасение UX. На 15 тысячах товарах — ускорение в 10 раз и больше.

    На практике: пример из админки каталога

    Типичная задача: есть таблица товаров, нужно отфильтровать по категории и статусу наличия. allowedIds из фильтра — 3000 товаров, основной список — 15000. Юзер меняет фильтр, данные должны обновиться без задержки.

    Старый способ (костыль):

    const filtered = allProducts.filter((item, index) => 
      allowedIds.indexOf(item.id) === index
    );
    

    Ждём, пока indexOf переберёт массив allowedIds для каждого товара. На каждое изменение фильтра — ~500ms зависания. Юзер кликает, видит freezing, раздражается.

    Новый способ (быстро):

    const allowedIdSet = new Set(allowedIds);
    const filtered = allProducts.filter(p => allowedIdSet.has(p.id));
    

    Есть дубли в списке товаров? Дополняем Map:

    const normalized = new Map();
    allProducts.forEach(p => {
      if (allowedIdSet.has(p.id)) {
        const existing = normalized.get(p.id);
        if (!existing || p.stock > existing.stock) {
          normalized.set(p.id, p); // берём вариант с большим stock
        }
      }
    });
    const filtered = Array.from(normalized.values());
    

    Результат: мгновенно, UI не зависает, юзер доволен. На этом примере ускорение ощущается физически.

    Когда filter + indexOf ещё используется

    Есть кейсы, когда filter + indexOf остаётся единственным разумным вариантом — например, если нужно сохранить порядок первого появления элемента и данные постоянно меняются. Set гарантирует уникальность, но не гарантирует порядок в старых браузерах (хотя в современных порядок вставки соблюдается).

    Ещё встречается старый легаси-код, который почему-то переписывать не хотят — работает, значит работает. Но если ты пишешь новый код и видишь filter + indexOf на больших массивах — это red flag. Это признак либо забывчивости, либо незнания особенностей производительности.

    Проблема в том, что на маленьких массивах (50-100 элементов) разница не видна, поэтому никто не замечает, пока не упрёшься в реальные данные. А потом начинаются поиски баг-репортов и оптимизации, которые можно было избежать с самого начала.

    Финальный ударный тест

    Итак, рецепт для нормализации товаров из API:

    • Если фильтруешь по ID — Set для индексирования, filter остаётся честным: new Set(allowedIds) + has() вместо indexOf().
    • Если удаляешь дубли полностью — Map по ID: new Map(products.map(p => [p.id, p])) + Array.from() в конце.
    • Если нужна сложная логика (выбор по критерию) — forEach с условием в момент set, дешевле, чем дополнительные циклы.

    Экономия на производительности — это не просто быстрее, это улучшение опыта пользователя. Мгновенный отклик на клик, отсутствие зависаний, гладкий UI. На фронте это заметно сразу, на бэке тоже, но там интеллектуальнее относятся к сложности алгоритмов.

    Одно последнее: профилируй на реальных данных. Может быть, у тебя массивы намного меньше, и разницы не будет вообще. Может быть, наоборот — 100 тысяч товаров, и Set сэкономит тебе секунды. Инструменты в браузере (Performance, DevTools) покажут истину быстрее, чем любые статьи. Но основной закон остаётся: O(n) лучше, чем O(n²), всегда и везде.

    1 ответ Последний ответ
    0

    Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.

    Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.

    С вашими комментариями этот пост мог бы стать ещё лучше 💗

    Зарегистрироваться Войти

    Категории

    • Главная
    • Новости
    • Фронтенд
    • Бекенд
    • Языки программирования

    Контакты

    • Сотрудничество
    • info@exlends.com

    © 2024 - 2026 ExLends, Inc. Все права защищены.

    Политика конфиденциальности
    • Войти

    • Нет учётной записи? Зарегистрироваться

    • Войдите или зарегистрируйтесь для поиска.
    • Первое сообщение
      Последнее сообщение
    0
    • Лента
    • Категории
    • Последние
    • Метки
    • Популярные
    • Пользователи
    • Группы