Как проверить, есть ли элемент в массиве JavaScript? От «просто работает» до «летает»
Вы когда-нибудь писали код и вдруг замирали, глядя на массив данных? «Есть ли здесь то, что мне нужно?» — этот вопрос мучает каждого разработчика. На Stack Overflow тема How do I check if an array includes a value in JavaScript собрала миллионы просмотров. И не зря. Казалось бы, тривиальная задача, но от её решения зависит не только читаемость кода, но и скорость работы всего приложения.
Сегодня мы разберём все способы проверки наличия элемента в массиве JavaScript. Мы не просто покажем методы — мы проведём настоящее расследование производительности. Вы узнаете, когда использовать includes(), почему indexOf() иногда быстрее, и как Set может ускорить ваш код в десятки раз. Поехали!
Классика жанра: Array.prototype.includes()
Самый современный и читаемый способ — метод includes(). Он появился в ES2016 и быстро стал стандартом де-факто. Почему? Потому что его название говорит само за себя.
const fruits = ['apple', 'banana', 'mango', 'orange'];
const hasBanana = fruits.includes('banana');
console.log(hasBanana); // true
const hasGrape = fruits.includes('grape');
console.log(hasGrape); // false
Всё просто и элегантно. includes() использует строгое сравнение (===), поэтому будьте внимательны с типами данных:
const numbers = [1, 2, 3];
console.log(numbers.includes('1')); // false — строка не равна числу
Плюсы:
- Читаемость 10/10 — код понятен даже новичку
- Работает с
NaN(в отличие отindexOf) - Поддерживается всеми современными браузерами
Минусы:
- Линейная сложность O(n) — проходит весь массив
- Не подходит, если нужно найти индекс элемента
Ветеран: Array.prototype.indexOf()
До появления includes() все использовали indexOf(). Этот метод возвращает индекс первого найденного элемента или -1, если элемент отсутствует.
const fruits = ['apple', 'banana', 'mango'];
const index = fruits.indexOf('banana');
if (index !== -1) {
console.log('Элемент найден на позиции', index);
} else {
console.log('Элемент не найден');
}
Многие привыкли к такой проверке, и она по-прежнему работает. Но есть один важный нюанс: indexOf() не умеет находить NaN.
const arr = [NaN];
console.log(arr.indexOf(NaN)); // -1 — не находит!
console.log(arr.includes(NaN)); // true — находит
Это связано с тем, что NaN !== NaN в JavaScript, и indexOf использует строгое сравнение. includes() же использует алгоритм SameValueZero, который корректно обрабатывает NaN.
Когда использовать indexOf?
- Если вам нужен индекс элемента, а не просто факт наличия
- Если вы уверены, что в массиве нет NaN
- В легаси-коде, где нельзя использовать ES2016+
Гибкий боец: Array.prototype.find() и findIndex()
Когда нужно проверить не просто наличие, а выполнение условия, на помощь приходит find(). Он принимает функцию-колбэк и возвращает первый элемент, удовлетворяющий условию, или undefined.
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
];
const user = users.find(u => u.age > 28);
console.log(user); // { id: 2, name: 'Bob', age: 30 }
// Проверка на существование
const exists = users.find(u => u.id === 4) !== undefined;
console.log(exists); // false
findIndex() работает аналогично, но возвращает индекс или -1.
const index = users.findIndex(u => u.name === 'Bob');
console.log(index); // 1
Плюсы:
- Мощная фильтрация по любому условию
- Работает с массивами объектов
Минусы:
- Медленнее, чем
includes()для простых типов - Избыточен, если нужно просто проверить примитив
Тяжёлая артиллерия: Set для многократных проверок
А теперь самое интересное — производительность. Что если вам нужно проверять наличие элементов тысячи раз? Например, при фильтрации большого массива или в цикле? Тут обычные методы массива начинают тормозить.
Выход — использовать Set. Эта структура данных обеспечивает поиск за O(1) в среднем, в отличие от O(n) для массивов.
// Плохо: проверка в цикле через includes
const blacklist = ['bad1', 'bad2', 'bad3', 'bad4'];
const users = ['user1', 'bad2', 'user3', 'bad4', 'user5'];
const filteredBad = users.filter(user => !blacklist.includes(user));
// Для каждого элемента users проходим по всему blacklist — O(n*m)
// Хорошо: конвертируем в Set
const blacklistSet = new Set(blacklist);
const filteredGood = users.filter(user => !blacklistSet.has(user));
// Проверка через has() — O(1) для каждого элемента — O(n)
Разница колоссальная. Давайте посмотрим на тесты производительности:
// Тест: массив из 10 000 элементов, 10 000 проверок
const bigArray = Array.from({ length: 10000 }, (_, i) => i);
const bigSet = new Set(bigArray);
console.time('Array.includes');
for (let i = 0; i < 10000; i++) {
bigArray.includes(i);
}
console.timeEnd('Array.includes'); // ~50-100ms
console.time('Set.has');
for (let i = 0; i < 10000; i++) {
bigSet.has(i);
}
console.timeEnd('Set.has'); // ~0.5-2ms
Set.has() быстрее в 50-100 раз! Это критично для высоконагруженных приложений, обработки данных в реальном времени или игр.
Дедовский метод: цикл for
Иногда старый добрый цикл for оказывается самым быстрым. Особенно если нужно прервать выполнение при первом совпадении.
function containsCustom(arr, value) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === value) return true;
}
return false;
}
const arr = [1, 2, 3, 4, 5];
console.log(containsCustom(arr, 3)); // true