Уникальные элементы в массиве JavaScript: удаляем дубликаты изнутри

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Массив-дубликат: почему эта задача собрала тысячи просмотров на Stack Overflow

Представьте: вы пишете интернет-магазин, пользователь добавляет товары в корзину. Но по ошибке или из-за бага один и тот же айфон оказывается в списке трижды. Или вы парсите список тегов из базы данных, а там "JavaScript" встречается 15 раз подряд. Что делать? Правильно — оставить только уникальные значения.

Вопрос "Get all unique values in a JavaScript array (remove duplicates)" на Stack Overflow собрал сотни тысяч просмотров. И это не случайно. Задача кажется простой, но под капотом скрывается целый арсенал подходов: от однострочников на современном ES6 до хитрых трюков с filter и indexOf. В этой статье мы не просто дадим готовые решения, а разберём, как каждое из них работает на уровне движка JavaScript.

Готовы заглянуть под капот? Поехали!


Способ №1: Set — король производительности

Самый элегантный и быстрый способ получить уникальные значения в массиве JavaScript — использовать объект Set. Он появился в ES6 и буквально создан для этой задачи.

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];

console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

Как это работает под капотом?

Объект Set хранит только уникальные значения. Когда мы передаём массив в конструктор new Set(numbers), движок JavaScript проходит по каждому элементу и добавляет его во внутреннюю хеш-таблицу. Если значение уже есть — оно игнорируется. Затем оператор расширения ... превращает Set обратно в массив.

Почему это быстро? Потому что поиск и вставка в Set выполняются за O(1) (константное время). Для массива из 10 000 элементов разница с другими методами будет колоссальной.

Важно: Set сохраняет порядок элементов, но работает только с примитивами (числа, строки, булевы значения). Для объектов сравнение идёт по ссылке, а не по содержимому.

Когда использовать?

  • Если массив состоит из чисел или строк.
  • Если важна скорость обработки больших массивов.
  • Если вы пишете современный код и не боитесь ES6.

Способ №2: filter + indexOf — классика жанра

Этот метод — ветеран JavaScript. Он работает во всех браузерах без полифиллов и до сих пор популярен на Stack Overflow.

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = numbers.filter((value, index, arr) => {
  return arr.indexOf(value) === index;
});

console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

Разбор логики

Метод filter создаёт новый массив, оставляя только те элементы, для которых колбэк возвращает true. Внутри колбэка мы проверяем: является ли текущий индекс первым вхождением этого значения в массив? Если да — оставляем элемент. Если значение уже встречалось раньше — indexOf вернёт меньший индекс, условие не выполнится, и дубликат будет отброшен.

Подвох: indexOf каждый раз пробегает по массиву с начала. Это даёт сложность O(n²). Для маленьких массивов (до 1000 элементов) разница незаметна, но на 100 000 элементах браузер может зависнуть на секунду.

Когда использовать?

  • Если нужно поддерживать старые браузеры (IE11).
  • Если массив небольшой.
  • Если вы хотите наглядно показать логику работы "первого вхождения".

Способ №3: reduce + аккумулятор — гибкость и контроль

Метод reduce даёт полный контроль над процессом. Мы сами решаем, как собирать результат.

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = numbers.reduce((acc, value) => {
  if (!acc.includes(value)) {
    acc.push(value);
  }
  return acc;
}, []);

console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

Как это работает под капотом?

Мы создаём пустой массив-аккумулятор ([]). На каждой итерации проверяем, есть ли текущее значение в аккумуляторе через includes(). Если нет — добавляем. Если есть — пропускаем.

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

Когда использовать?

  • Когда нужно не просто удалить дубликаты, но и преобразовать данные.
  • Если вы пишете функциональный код и любите цепочки методов.
  • Для учебных целей — наглядно видно, как работает аккумулятор.

Способ №4: forEach + объект-словарь — суперскорость без Set

Хотите скорость, как у Set, но без использования ES6? Используйте объект как хеш-таблицу.

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [];
const seen = {};

numbers.forEach(value => {
  if (!seen[value]) {
    seen[value] = true;
    uniqueNumbers.push(value);
  }
});

console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

Разбор механики

Мы создаём пустой объект seen. На каждой итерации проверяем, есть ли ключ с таким значением в объекте. Доступ к свойству объекта — это O(1). Если ключа нет — добавляем его и пушим значение в результирующий массив.

Важное предостережение: Этот метод преобразует все ключи в строки. Число 1 и строка "1" будут считаться одинаковыми. Для смешанных типов данных это может стать проблемой.

Когда использовать?

  • Когда нужна максимальная производительность, но Set недоступен.
  • Для массивов с примитивами одного типа.
  • В легаси-проектах без транспиляции.

Способ №5: Map — для сложных случаев

Если вам нужно удалить дубликаты из массива объектов, сравнивая их по определённому свойству, Map придёт на помощь.

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1,                

Блоги

Рекомендаций по книгам