Получить все уникальные значения в JavaScript массиве (удалить дубликаты)
У меня есть массив чисел, в котором нужно убедиться, что все значения уникальны. Я нашел приведенный ниже фрагмент кода в интернете, и он отлично работает, пока в массиве нет нуля. Я нашел другой скрипт, который выглядит почти так же, но он не дает сбоя.
Как можно определить, где именно прототипный скрипт дает сбой?
Array.prototype.getUnique = function() {
var o = {}, a = [], i, e;
for (i = 0; e = this[i]; i++) {o[e] = 1};
for (e in o) {a.push (e)};
return a;
}
Проблема в вашем методе Array.prototype.getUnique возникает из-за того, как JavaScript оценивает условия в цикле for. Проблема заключается в этой строке: for (i = 0; e = this[i]; i++) {o[e] = 1}. Когда массив содержит ноль, цикл преждевременно завершается, потому что ноль является falsy в JavaScript.
Вот почему это происходит: условие e = this[i] присваивает элемент массива переменной e и возвращает это значение. Если значение равно нулю (что является falsy), цикл прекращает выполнение, даже если после нуля могут быть другие элементы, которые нужно обработать.
Содержание
- Понимание проблемы
- Почему ноль прерывает цикл
- Несколько решений
- Сравнение производительности
- Лучшие практики
- Альтернативные подходы
Понимание проблемы
Проблемный код использует цикл for с условием, которое зависит от истинности присвоенного значения:
Array.prototype.getUnique = function() {
var o = {}, a = [], i, e;
for (i = 0; e = this[i]; i++) {o[e] = 1}; // Проблема здесь!
for (e in o) {a.push (e)};
return a;
}
Когда вы тестируете это с массивом, содержащим ноль, вы увидите проблему:
const testArray = [1, 2, 0, 3, 2, 4];
console.log(testArray.getUnique()); // Ожидаемый: [1, 2, 0, 3, 4]
// Фактический: [1, 2]
Цикл останавливается на индексе 2 (где значение равно 0) и никогда не обрабатывает оставшиеся элементы.
Почему ноль прерывает цикл
В JavaScript существуют определенные истинные (truthy) и ложные (falsy) значения:
Истинные значения (truthy):
- Ненулевые числа
- Непустые строки
- Объекты
- Массивы
- Функции
true
Ложные значения (falsy):
0,-0,0.0(все нулевые числа)- Пустая строка
"" nullundefinedfalse
В вашем коде:
for (i = 0; e = this[i]; i++) {
// Когда this[i] равно 0, e становится равным 0, что является falsy
// Поэтому условие цикла не выполняется и цикл останавливается
}
Несколько решений
Решение 1: Использование длины массива вместо истинности значения
Самый простой способ исправить — использовать длину массива в условии:
Array.prototype.getUnique = function() {
var o = {}, a = [], i, e;
// Исправлено: используем i < this.length вместо e = this[i]
for (i = 0; i < this.length; i++) {o[this[i]] = 1};
for (e in o) {a.push (e)};
return a;
}
Решение 2: Явная проверка на undefined
Другой подход — явно проверять на undefined:
Array.prototype.getUnique = function() {
var o = {}, a = [], i, e;
for (i = 0; e = this[i]; i++) {
// Прерываем только если действительно достигли конца
if (typeof e === 'undefined') break;
o[e] = 1;
}
for (e in o) {a.push (e)};
return a;
}
Решение 3: Использование современных методов JavaScript
Для более чистого кода рассмотрите использование современных подходов JavaScript:
// Использование Set (ES6)
Array.prototype.getUnique = function() {
return [...new Set(this)];
}
// Использование filter и indexOf
Array.prototype.getUnique = function() {
return this.filter((item, index) => {
return this.indexOf(item) === index;
});
}
Сравнение производительности
Вот сравнение разных подходов:
| Метод | Временная сложность | Сложность по памяти | Обрабатывает ноль? |
|---|---|---|---|
| Оригинальный (проблемный) | O(n) | O(n) | ❌ Нет |
| Исправленная версия | O(n) | O(n) | ✅ Да |
| Подход с Set | O(n) | O(n) | ✅ Да |
| Filter/IndexOf | O(n²) | O(n) | ✅ Да |
Исправленная версия и подход с Set оптимальны по производительности. Метод filter/IndexOf менее эффективен для больших массивов из-за сложности O(n²).
Лучшие практики
При работе с уникальностью массивов в JavaScript:
-
Избегайте изменения Array.prototype, если это абсолютно необходимо, так как это может повлиять на все массивы в вашем приложении и потенциально вызвать конфликты.
-
Используйте подход с Set для современных сред JavaScript (ES6+):
function getUniqueArray(arr) {
return Array.from(new Set(arr));
}
-
Учитывайте типы данных — если у вас есть объекты или сложные значения, вам понадобится другой подход.
-
Учитывайте поддержку браузеров — Set поддерживается во всех современных браузерах, но не в старых, таких как IE.
Альтернативные подходы
Для массивов объектов
Если вам нужно удалить дубликаты из массивов объектов на основе свойства:
function getUniqueObjects(arr, key) {
const seen = new Set();
return arr.filter(obj => {
const value = obj[key];
if (seen.has(value)) {
return false;
}
seen.add(value);
return true;
});
}
Для смешанных типов данных
Если ваш массив содержит разные типы данных и вы хотите сохранить типы:
function getUniqueMixed(arr) {
const seen = new Map();
return arr.filter(item => {
const key = typeof item + JSON.stringify(item);
if (seen.has(key)) {
return false;
}
seen.set(key, true);
return true;
});
}
Ключевой вывод: при использовании выражений присваивания в условиях цикла всегда учитывайте, какие значения могут быть falsy в JavaScript, и отдавайте предпочтение явным проверкам длины или другим более надежным подходам.
Источники
Заключение
- Проблема возникает потому, что ноль является falsy в JavaScript, что приводит к преждевременному сбою условия цикла
- Самое простое исправление — изменение
e = this[i]наi < this.lengthв условии цикла for - Современный JavaScript предоставляет более чистые решения с использованием объектов Set
- Будьте осторожны при изменении Array.prototype, так как это может иметь побочные эффекты
- Учитывайте производственные последствия разных подходов, особенно для больших массивов
- В большинстве случаев рекомендуется подход с Set (
[...new Set(arr)]) из-за его простоты и производительности
Понимание оценки truthy/falsy в JavaScript позволяет избежать подобных проблем в вашем коде и выбрать наиболее подходящий метод для удаления дубликатов из массивов.