Как удалить дубликаты из массива в JavaScript? Самые эффективные способы
1. Использование Set (самый простой и быстрый способ)
Самый элегантный и производительный способ для примитивных типов (числа, строки, булевы значения) — использовать встроенный объект Set. Он автоматически хранит только уникальные значения. Комбинируя его с оператором spread (...) или Array.from(), вы получаете новый массив без дубликатов.
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
// Альтернативная запись:
const uniqueNumbers2 = Array.from(new Set(numbers));
console.log(uniqueNumbers2); // [1, 2, 3, 4, 5]
Почему это работает? Объект Set гарантирует, что каждое значение встречается только один раз. При создании new Set(numbers) все дубликаты автоматически отбрасываются. Затем мы преобразуем Set обратно в массив.
Важно: Этот метод работает только для примитивных значений. Для объектов (массивов, словарей) сравнение происходит по ссылке, а не по содержимому, поэтому дубликаты-объекты не будут удалены корректно.
2. Использование filter() и indexOf()
Классический подход, который хорошо подходит для обучения и понимания логики. Метод filter() создает новый массив, а indexOf() возвращает первый индекс найденного элемента. Если текущий индекс элемента совпадает с его первым вхождением — это уникальный элемент.
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = numbers.filter((value, index, self) => {
return self.indexOf(value) === index;
});
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
Плюсы: Понятный код, легко читается новичками.
Минусы: Медленнее, чем Set, особенно на больших массивах (сложность O(n²)), так как indexOf() каждый раз проходит по массиву.
3. Использование 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() работает быстрее indexOf() для поиска, но всё равно имеет сложность O(n) на каждой итерации.
4. Удаление дубликатов для объектов (по ключу)
Если у вас массив объектов и нужно удалить дубликаты по конкретному свойству, Set не подойдет. Используйте Map или объект с ключами:
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }, // дубликат по id
{ id: 3, name: 'Charlie' }
];
// Удаляем дубликаты по полю id
const uniqueUsers = Array.from(
new Map(users.map(user => [user.id, user])).values()
);
console.log(uniqueUsers);
// Результат: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]
Объяснение: Метод Map использует id как ключ. Если встречается дубликат, он перезаписывает предыдущее значение. Затем мы берем только значения (.values()) и преобразуем в массив.
Сравнение производительности (для массива из 100 000 элементов)
- Set: ~1-3 мс — самый быстрый
- filter + indexOf: ~2000-5000 мс — очень медленный
- reduce + includes: ~100-300 мс — средний
- Map для объектов: ~5-10 мс — быстрый
Для 99% случаев используйте Set — это современный стандарт. Если нужно сохранить порядок или работать с объектами — используйте Map или reduce.
Альтернативный подход: Lodash и однострочники
Если вы используете библиотеку Lodash (часто встречается в крупных проектах), задача решается одной функцией:
const _ = require('lodash');
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = _.uniq(numbers);
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
// Для объектов по ключу:
const users = [{id:1}, {id:2}, {id:1}];
const uniqueUsers = _.uniqBy(users, 'id');
console.log(uniqueUsers); // [{id:1}, {id:2}]
Плюсы: Кратко, читаемо, протестировано тысячами разработчиков. Lodash также оптимизирован для производительности.
Минусы: Добавляет зависимость (около 70 КБ в сжатом виде). Для простых задач это может быть избыточно.
Исправление вашего первоначального кода
Ваш код с вложенными циклами и splice имеет ошибку сдвига индексов. Вот исправленная версия:
function removeDuplicates(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--; // Корректируем индекс после удаления
}
}
}
return arr;
}
Добавление j-- решает проблему, но такой подход всё равно медленный (O(n²)) и мутирует исходный массив. Лучше использовать неизменяемые методы, которые возвращают новый массив.
Для очень больших массивов (миллионы элементов)
Если производительность критична, рассмотрите использование TypedArray или Web Workers. Но для 99% задач Set или _.uniq() будет достаточно.