Avatar ·

Как удалить дубликаты из массива и получить только уникальные значения в JavaScript?

Всем привет! Я новичок в JavaScript и столкнулся с проблемой при работе с массивами. У меня есть массив, содержащий повторяющиеся значения. Мне нужно получить из него новый массив, в котором останутся только уникальные элементы, то есть без дубликатов. Например, у меня есть такой массив:
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'kiwi', 'orange'];
Я хочу получить на выходе: ['apple', 'banana', 'orange', 'kiwi'] — то есть каждый фрукт встречается только один раз, а порядок сохраняется по первому вхождению. Я пробовал написать свою функцию с циклом и проверкой через indexOf, но код получился громоздким и не очень читаемым. Мне кажется, что в современном JavaScript должны быть более элегантные способы сделать это. Подскажите, пожалуйста, какие существуют методы для удаления дубликатов из массива? Какие из них считаются лучшими по производительности и читаемости? Интересуют решения как для массивов примитивов (строки, числа), так и для массивов объектов, если это возможно. Заранее спасибо за помощь! <<>>

1. Использование Set (самый простой и быстрый способ)

Set — это встроенная структура данных в JavaScript, которая автоматически хранит только уникальные значения. Если передать массив в конструктор Set, дубликаты будут удалены. Затем мы преобразуем Set обратно в массив с помощью оператора spread (...) или Array.from().

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'kiwi', 'orange'];
const uniqueFruits = [...new Set(fruits)];
console.log(uniqueFruits); // ['apple', 'banana', 'orange', 'kiwi']

Почему это работает:

  • new Set(fruits) создает коллекцию, где каждый элемент гарантированно уникален. Для примитивов (строки, числа) сравнение идет по значению.
  • Оператор ... (spread) разворачивает Set в элементы нового массива.

Преимущества:

  • Максимально лаконичный код (одна строка).
  • Высокая производительность (O(n) — линейная сложность).
  • Работает с любыми примитивами: числами, строками, символами.

Важно: Set не удаляет дубликаты объектов, так как объекты сравниваются по ссылке, а не по содержимому. Для объектов потребуется другой подход.

2. Использование filter() + indexOf() (классический метод)

Этот способ проверяет, является ли текущий элемент первым вхождением в массиве. Если indexOf(element) равен текущему индексу, значит элемент встречается впервые.

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

Как это работает:

  • filter проходит по каждому элементу.
  • indexOf возвращает индекс первого вхождения значения.
  • Если он совпадает с текущим индексом — элемент уникален, и мы его сохраняем.

Недостаток: indexOf внутри цикла делает метод O(n²) по времени, что медленно для больших массивов (более 10 000 элементов).

3. Использование reduce() + аккумулятор (гибкий способ)

Этот метод позволяет контролировать процесс и легко адаптируется под разные типы данных.

const items = ['a', 'b', 'a', 'c', 'b'];
const uniqueItems = items.reduce((acc, item) => {
    if (!acc.includes(item)) {
        acc.push(item);
    }
    return acc;
}, []);
console.log(uniqueItems); // ['a', 'b', 'c']

Плюсы: Читаемый код, легко модифицируется (например, можно добавить проверку на глубокое сравнение объектов).
Минусы: includes тоже имеет сложность O(n), поэтому общая сложность O(n²).

4. Для массивов объектов — с помощью Map или Set с ключом

Объекты нельзя сравнивать напрямую через Set. Но можно использовать Map или Set с сериализацией (например, по свойству id):

const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 1, name: 'Alice' }, // дубликат
    { id: 3, name: 'Charlie' }
];

// Удаляем дубликаты по id
const uniqueUsers = [...new Map(users.map(item => [item.id, item])).values()];
console.log(uniqueUsers);
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]

Объяснение:

  • users.map(item => [item.id, item]) создает массив пар [ключ, значение].
  • new Map(...) автоматически оставляет только последнюю запись с уникальным ключом.
  • .values() извлекает объекты, а [...] преобразует их в массив.

Рекомендация

Для большинства случаев используйте Set — это современный, быстрый и читаемый способ. Для массивов объектов с уникальным идентификатором — комбинацию Map + map. Избегайте filter + indexOf на больших массивах из-за низкой производительности.

<<>>

Альтернативный подход: использование библиотеки Lodash

Если вы работаете над проектом, где уже используется библиотека Lodash, вы можете воспользоваться её функцией _.uniq(). Для массивов объектов есть _.uniqBy(), которая позволяет указать критерий уникальности (например, поле объекта).

// Для примитивов
const numbers = [1, 2, 3, 2, 4, 3];
const unique = _.uniq(numbers); // [1, 2, 3, 4]

// Для объектов по полю id
const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 1, name: 'Alice' }
];
const uniqueUsers = _.uniqBy(users, 'id');
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

Плюсы:

  • Простота и читаемость (одна функция).
  • Поддержка сложных случаев (глубокое сравнение, сравнение по нескольким полям).
  • Хорошо протестирована и оптимизирована.

Минусы:

  • Дополнительная зависимость в проекте (размер библиотеки ~24 КБ в сжатом виде).
  • Излишне, если нужно только удалить дубликаты — нативный Set справляется лучше.

Когда использовать: Если вы уже используете Lodash в проекте или вам нужно удалять дубликаты из массива объектов по сложному критерию (например, по комбинации полей).