findIndex против for + break: поиск товара по ID без полного скана каталога и багов с -1
-
Каждый день фронтендеры роются в каталогах товаров - массив из тысяч объектов. Ищут по ID, а код тормозит на больших данных. Разберём findIndex против классического for с break: где скорость, где читаемость, и как не словить -1 в неожиданном месте.
Это не теория - практика из реальных приложений. Поможет выбрать инструмент под задачу, избежать утечек производительности и типичных косяков с возвращаемыми значениями. Заодно разберём, почему новички любят forEach, а потом удивляются лагам.
Почему findIndex бьёт forEach и filter на больших каталогах
Методы массива типа findIndex и find останавливаются на первом совпадении. Никаких лишних итераций - нашли товар по ID, вернули индекс и вышли. А forEach или filter пробегают весь каталог, даже если нужный объект на нулевом месте. Это классическая утечка циклов, когда код выглядит функционально, но жрёт CPU зря.
Представь каталог на 10k товаров. findIndex с предикатом
item => item.id === targetIdвернёт 42 или -1, не трогая хвост массива. for + break делает то же самое вручную, но под полным контролем. Тесты показывают: for выигрывает на миллионах элементов, findIndex близко следует за ним. А filter? Создаст новый массив со всеми товарами - память в трубу.- Преимущества findIndex: читаемый код, встроенная логика выхода, работает с any[], не требует ручного индекса.
- Когда for с break: критичные hot paths, где каждая микросекунда на счету, или legacy-код без ES6.
- Антипаттерн forEach: всегда до конца, даже после console.log(‘найден!’). Не для поиска.
Метод Возврат Остановка? Скорость на 100k элементов findIndex индекс или -1 да, на первом высокая for + break индекс да, на break максимальная forEach undefined нет низкая для поиска filter новый массив нет медленная, жрёт память Типичные баги с -1 и как их словить заранее
findIndex вернёт -1, если ничего не нашёл. Легко забыть проверку: код думает, что индекс -1 валиден, и падает на array[-1]. Или путают с find(), который кидает undefined - там свой set багов. А в for-цикле ты сам контролируешь: if (found) break, else return -1.
Реальный пример: корзина товаров, ищем по ID для апдейта. findIndex даёт 5, но типизация слабая - вдруг id не number? Строгое === спасает от ‘123’ == 123. В for можно добавить тип-чек внутри цикла. Ещё засада: мутирующий массив во время поиска. findIndex клонирует логику, но не защищает от splice в колбэке.
- Баг #1:
if (idx = array.findIndex(...))- присвоение вместо сравнения, всегда truthy. - Баг #2: Нет проверки
if (idx !== -1)перед array[idx] - обращение к несуществующему элементу.
В TypeScript: укажи тип возвратаnumber | -1, ESLint подскажет. - Баг #3: Предикат с побочками -
item.id === id && item.price *= 0.9мутирует данные.
const idx = catalog.findIndex(item => item.id === targetId && item.id > 0); if (idx > -1) { // safe update } else { console.warn('Товар не найден'); }For + break под капотом: микро-оптимизация без фреймворков
for (let i = 0; i < catalog.length; i++) { if (catalog[i].id === targetId) return i; } return -1; Простой, как велосипед. Нет overhead от Function конструктора, нет замыканий, чистый ивент-луп без вмешательств. findIndex под капотом примерно то же - C++ цикл в V8 с early exit.
Разница в бандле: findIndex - 0 байт, чистый JS. for такой же. Но в минифицированном коде for короче на символы. На мобильных каталогах 50k+ for выигрывает 10-20%. Ещё плюс: легко добавить условия - проверить stock > 0, без переписывания предиката.
- Микро-версия findIndex:
function myFindIndex(arr, pred) { for (let i = 0; i < arr.length; i++) { if (pred(arr[i], i, arr)) return i; } return -1; }- Тестируй на реальных данных: Array.from({length: 1e5}, (_, i) => ({id: i}));
- Профиль: Chrome DevTools покажет, где тормозит - в колбэке или цикле.
Сценарий findIndex for + break Рекомендация Малый массив (<1k)
читаемо
простоfindIndex Большой каталог близко к for
быстрееfor С мутациями риск контроль for findIndex в связке с другими методами: не всё так просто
Часто ищут не только индекс. Хочешь удалить товар? findIndex + splice. Обновить? findIndex + mutate. Но splice на больших массивах - O(n) сдвиг, лучше filter для immutable. Или Map по ID - O(1) поиск, но жрёт память.
Ещё нюанс: sparse arrays или удалённые элементы (delete arr). findIndex их пропустит? Нет, перебирает индексы последовательно. В for то же. Но с Map/Set никаких дыр. Типичный косяк: поиск по строковому ID с Number(id), когда id - number.
- Комбо #1:
splice(items.findIndex(i => i.id === id), 1)- удаление по ID.
Immutable:filter(item => item.id !== id)- новый массив. - Комбо #2:
const idx = findIndex(...); items[idx]?.price += delta;- optional chaining спасает от -1. - Когда Map лучше: частые lookup’ы, >10k элементов.
Бей по костылям: выбирай цикл под задачу
В итоге findIndex - для читаемого кода в 80% случаев. for + break - когда секунды тикают в large-scale apps. Главное - всегда проверяй на -1, пиши предикаты без side-effects и профилируй на реальных данных.
Осталось за кадром: weak maps для GC-safe кэша ID, или как V8 оптимизирует inline-колбэки. Подумать стоит над тем, чтобы вынести поиск в worker для mega-каталогов - main thread вздохнёт свободнее.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.