<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Filter + indexOf vs Set: как быстро нормализовать товары из API]]></title><description><![CDATA[<p dir="auto">Когда с API летит список товаров с дублями по ID, нужно их убрать — и желательно без лагов на фронте. Классический подход <code>filter</code> + <code>indexOf</code> работает, но сожрёт производительность на больших объёмах. Set выглядит проще, но с объектами по ссылкам он бесполезен. Разбираемся, почему один способ быстрее другого в 10 раз, и как не наступить на грабли.</p>
<h2>Почему filter + indexOf — это костыль на больших данных</h2>
<p dir="auto">Все начинается просто: нужно отфильтровать массив и оставить только первое вхождение каждого элемента. Код выглядит честно и понятно:</p>
<pre><code class="language-javascript">const filtered = products.filter((item, index) =&gt; 
  products.indexOf(item.id) === index
);
</code></pre>
<p dir="auto">Смотрится красиво, но под капотом происходит <strong>O(n²)</strong> ужас. Для каждого элемента в цикле filter мы снова прошиваем весь массив через indexOf, ища его первое вхождение. На 15 тысячах товаров это 225 миллионов операций сравнения. Плюс indexOf работает с ссылками, поэтому два объекта с одинаковым ID будут считаться разными — нужна дополнительная логика через JSON.stringify, что ещё больше замедляет.</p>
<p dir="auto">Результат: фильтр по категории в админке лагает, юзеры видят висячий UI, всем грустно. Когда allowedIds из стора — это 3 тысячи штук, а основной список 15 тысяч, каждое изменение фильтра превращается в пытку.</p>
<p dir="auto">Проблема в том, что <strong>indexOf вызывается для каждого элемента</strong>, и каждый раз он ползёт по всему массиву заново. Никакой оптимизации, никакой памяти о том, что ты уже ищешь. Просто тупо O(n) × O(n) = боль.</p>
<h2>Set: волшебство с примитивами, разочарование с объектами</h2>
<p dir="auto">Set — это структура данных, которая хранит только уникальные значения и даёт доступ за O(1). Звучит как решение всех проблем:</p>
<pre><code class="language-javascript">const uniqueIds = new Set(bigArray.map(id =&gt; id));
const filtered = products.filter(p =&gt; uniqueIds.has(p.id));
</code></pre>
<p dir="auto">Это <strong>O(n)</strong> вместо O(n²), и разница ощущается сразу — мгновенно вместо лагов. С примитивами (числа, строки) Set работает идеально: добавляешь элемент, Set сам проверяет, нет ли его уже, и хранит только уникальные.</p>
<p dir="auto">Но есть нюанс, который многие пропускают. Set проверяет равенство по значению для примитивов, но <strong>для объектов — по ссылке</strong>. Два объекта с одинаковым ID будут считаться разными, потому что это разные экземпляры в памяти. Поэтому просто так set(objects) не сработает — нужно класть в Set ID, а не сами объекты.</p>
<p dir="auto">Ещё один момент: если хранить строки длинных ID (например, UUID), могут быть хэш-коллизии при нехватке памяти, но это редкая проблема в реальных приложениях. Главное — Set безопасен и быстр, если использовать его правильно.</p>
<h2>Правильный паттерн: Set для индексирования, Map для объектов</h2>
<p dir="auto">Для нормализации товаров из API нужен <strong>гибридный подход</strong>. Если нужно отфильтровать объекты по ID, используй Set только для ID:</p>
<pre><code class="language-javascript">const allowedIds = new Set(categories.map(c =&gt; c.id));
const filtered = products.filter(p =&gt; allowedIds.has(p.id));
</code></pre>
<p dir="auto">Это читаемо, быстро, и нет магии. Фильтр остаётся простым, все условия явные.</p>
<p dir="auto">Если же нужно полностью нормализовать список и убрать дублирующиеся объекты, <strong>Map по ID</strong> — золотая середина:</p>
<pre><code class="language-javascript">const normalized = new Map(products.map(p =&gt; [p.id, p]));
const result = Array.from(normalized.values());
</code></pre>
<p dir="auto">Мап сам отсекает дубли при set: если два объекта с одинаковым ID, второй перезапишет первый. О(n) на создание, O(1) на доступ. Если нужна логика типа «оставить последний по дате» или «выбрать по какому-то критерию», можно добавить условие в момент set:</p>
<pre><code class="language-javascript">products.forEach(p =&gt; {
  const existing = normalized.get(p.id);
  if (!existing || p.updatedAt &gt; existing.updatedAt) {
    normalized.set(p.id, p);
  }
});
</code></pre>
<h2>Сравнение на реальных данных</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Подход</th>
<th>Сложность</th>
<th>Скорость на 15k товаров</th>
<th>С объектами</th>
<th>Читаемость</th>
</tr>
</thead>
<tbody>
<tr>
<td>filter + indexOf</td>
<td>O(n²)</td>
<td>Лаги, ~500ms+</td>
<td>Требует JSON.stringify</td>
<td>Средняя</td>
</tr>
<tr>
<td>Set (только ID)</td>
<td>O(n)</td>
<td>Мгновенно, ~5ms</td>
<td>Работает, но нужен отдельный фильтр</td>
<td>Высокая</td>
</tr>
<tr>
<td>Map по ID</td>
<td>O(n)</td>
<td>Мгновенно, ~8ms</td>
<td>Работает идеально, дубли отсекаются</td>
<td>Высокая</td>
</tr>
<tr>
<td>filter + Set</td>
<td>O(n)</td>
<td>Мгновенно, ~3ms</td>
<td>Работает, но хак</td>
<td>Хорошая</td>
</tr>
</tbody>
</table>
<p dir="auto">Видишь разницу? С Set и Map мы уходим с O(n²) на O(n) — это не просто ускорение, это спасение UX. На 15 тысячах товарах — ускорение в 10 раз и больше.</p>
<h2>На практике: пример из админки каталога</h2>
<p dir="auto">Типичная задача: есть таблица товаров, нужно отфильтровать по категории и статусу наличия. allowedIds из фильтра — 3000 товаров, основной список — 15000. Юзер меняет фильтр, данные должны обновиться без задержки.</p>
<p dir="auto"><strong>Старый способ (костыль):</strong></p>
<pre><code class="language-javascript">const filtered = allProducts.filter((item, index) =&gt; 
  allowedIds.indexOf(item.id) === index
);
</code></pre>
<p dir="auto">Ждём, пока indexOf переберёт массив allowedIds для каждого товара. На каждое изменение фильтра — ~500ms зависания. Юзер кликает, видит freezing, раздражается.</p>
<p dir="auto"><strong>Новый способ (быстро):</strong></p>
<pre><code class="language-javascript">const allowedIdSet = new Set(allowedIds);
const filtered = allProducts.filter(p =&gt; allowedIdSet.has(p.id));
</code></pre>
<p dir="auto">Есть дубли в списке товаров? Дополняем Map:</p>
<pre><code class="language-javascript">const normalized = new Map();
allProducts.forEach(p =&gt; {
  if (allowedIdSet.has(p.id)) {
    const existing = normalized.get(p.id);
    if (!existing || p.stock &gt; existing.stock) {
      normalized.set(p.id, p); // берём вариант с большим stock
    }
  }
});
const filtered = Array.from(normalized.values());
</code></pre>
<p dir="auto">Результат: мгновенно, UI не зависает, юзер доволен. На этом примере ускорение ощущается физически.</p>
<h2>Когда filter + indexOf ещё используется</h2>
<p dir="auto">Есть кейсы, когда filter + indexOf остаётся единственным разумным вариантом — например, если <strong>нужно сохранить порядок первого появления</strong> элемента и данные постоянно меняются. Set гарантирует уникальность, но не гарантирует порядок в старых браузерах (хотя в современных порядок вставки соблюдается).</p>
<p dir="auto">Ещё встречается старый легаси-код, который почему-то переписывать не хотят — работает, значит работает. Но если ты пишешь новый код и видишь filter + indexOf на больших массивах — это red flag. Это признак либо забывчивости, либо незнания особенностей производительности.</p>
<p dir="auto">Проблема в том, что на маленьких массивах (50-100 элементов) разница не видна, поэтому никто не замечает, пока не упрёшься в реальные данные. А потом начинаются поиски баг-репортов и оптимизации, которые можно было избежать с самого начала.</p>
<h2>Финальный ударный тест</h2>
<p dir="auto">Итак, рецепт для нормализации товаров из API:</p>
<ul>
<li><strong>Если фильтруешь по ID</strong> — Set для индексирования, filter остаётся честным: <code>new Set(allowedIds)</code> + <code>has()</code> вместо <code>indexOf()</code>.</li>
<li><strong>Если удаляешь дубли полностью</strong> — Map по ID: <code>new Map(products.map(p =&gt; [p.id, p]))</code> + <code>Array.from()</code> в конце.</li>
<li><strong>Если нужна сложная логика</strong> (выбор по критерию) — forEach с условием в момент set, дешевле, чем дополнительные циклы.</li>
</ul>
<p dir="auto">Экономия на производительности — это не просто быстрее, это <strong>улучшение опыта пользователя</strong>. Мгновенный отклик на клик, отсутствие зависаний, гладкий UI. На фронте это заметно сразу, на бэке тоже, но там интеллектуальнее относятся к сложности алгоритмов.</p>
<p dir="auto">Одно последнее: <strong>профилируй на реальных данных</strong>. Может быть, у тебя массивы намного меньше, и разницы не будет вообще. Может быть, наоборот — 100 тысяч товаров, и Set сэкономит тебе секунды. Инструменты в браузере (Performance, DevTools) покажут истину быстрее, чем любые статьи. Но основной закон остаётся: O(n) лучше, чем O(n²), всегда и везде.</p>
]]></description><link>https://forum.exlends.ru/topic/2054/filter-indexof-vs-set-kak-bystro-normalizovat-tovary-iz-api</link><generator>RSS for Node</generator><lastBuildDate>Wed, 20 May 2026 20:28:17 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/2054.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 13 Apr 2026 15:04:54 GMT</pubDate><ttl>60</ttl></channel></rss>