sort() с компаратором: сортируем правильно
sort() без аргументов — это ловушка для новичков. Числа сортируются «как строки» и результат вас удивит. Разберём почему так происходит и как написать правильный компаратор.
🐛 Ловушка: sort() сортирует как строки
Попробуйте угадать, что выведет этот код:
const nums = [10, 2, 1, 20, 3];
nums.sort();
console.log(nums); // Что получим?
Многие скажут: [1, 2, 3, 10, 20]. Но реальный ответ — [1, 10, 2, 20, 3].
Почему?! Потому что sort() по умолчанию преобразует элементы в строки и сортирует их лексикографически (как слова в словаре). В строках «10» идёт раньше «2», потому что символ «1» стоит перед «2» в алфавите.
⚠️ Классическая ловушка: [10, 2, 1].sort() → [1, 10, 2]. Это не баг JavaScript — это документированное поведение. sort() без компаратора сортирует строки. Для чисел всегда нужен компаратор!
Ещё примеры странного поведения без компаратора:
// Все примеры без компаратора
[1, 10, 100, 2, 20, 200].sort()
// → [1, 10, 100, 2, 20, 200] — "лексикографически" верно, численно нет!
["яблоко", "Банан", "вишня"].sort()
// → ["Банан", "вишня", "яблоко"] — заглавные раньше строчных!
[true, false, true].sort()
// → [false, true, true] — вроде работает, но случайно
🎛️ Компаратор: как объяснить sort порядок
Компаратор — это функция, которую вы передаёте в sort(). Она принимает два элемента a и b и говорит, кто из них должен идти раньше:
a идёт ПЕРЕД b (a останется слева)
Порядок между a и b не меняется
b идёт ПЕРЕД a (b встанет слева от a)
Для сортировки чисел существует простой трюк — вычесть одно из другого:
const nums = [10, 2, 1, 20, 3];
// По возрастанию: a - b
// Если a < b → отрицательное → a перед b ✓
nums.sort((a, b) => a - b);
console.log(nums); // → [1, 2, 3, 10, 20] — правильно!
// По убыванию: b - a (просто меняем местами)
nums.sort((a, b) => b - a);
console.log(nums); // → [20, 10, 3, 2, 1]
Почему a - b работает? Давайте проверим логику:
- Если
a = 2иb = 10:2 - 10 = -8(отрицательное → 2 перед 10) ✓ - Если
a = 10иb = 2:10 - 2 = 8(положительное → 2 перед 10) ✓ - Если
a = b:0(порядок не меняется) ✓
🔤 Сортировка строк правильно
Для строк на русском языке лучше использовать localeCompare — этот метод умеет сравнивать строки с учётом языка и регистра:
const cities = ["Казань", "Москва", "Архангельск", "Владивосток"];
// Без localeCompare — работает, но может удивить с кириллицей
cities.sort((a, b) => a > b ? 1 : a < b ? -1 : 0);
// С localeCompare — правильный способ для русских строк
cities.sort((a, b) => a.localeCompare(b, "ru"));
console.log(cities);
// → ["Архангельск", "Владивосток", "Казань", "Москва"]
// По убыванию
cities.sort((a, b) => b.localeCompare(a, "ru"));
// → ["Москва", "Казань", "Владивосток", "Архангельск"]
👥 Сортировка массива объектов
Самый частый кейс в реальном коде — отсортировать массив объектов по какому-то свойству:
const students = [
{ name: "Карл", grade: 75 },
{ name: "Алиса", grade: 92 },
{ name: "Диана", grade: 88 },
{ name: "Боб", grade: 65 }
];
// Сортировка по оценке по возрастанию
students.sort((a, b) => a.grade - b.grade);
console.log(students.map(s => `${s.name}: ${s.grade}`));
// → ["Боб: 65", "Карл: 75", "Диана: 88", "Алиса: 92"]
// По убыванию (лучшие первые)
students.sort((a, b) => b.grade - a.grade);
// → ["Алиса: 92", "Диана: 88", "Карл: 75", "Боб: 65"]
// По имени (алфавитно)
students.sort((a, b) => a.name.localeCompare(b.name, "ru"));
// → ["Алиса", "Боб", "Диана", "Карл"]
⚠️ ОСТОРОЖНО: sort изменяет исходный массив! В отличие от map, filter и reduce, метод sort мутирует (изменяет) исходный массив прямо на месте. Если вам нужен отсортированный массив, не трогая оригинал — сделайте копию!
📋 Сортировка без изменения исходного массива
Если нужно отсортировать, не трогая оригинал, создайте копию массива перед вызовом sort. Есть несколько способов:
const original = [3, 1, 4, 1, 5, 9, 2, 6];
// Способ 1: spread-оператор (современный, чаще всего используют)
const sorted1 = [...original].sort((a, b) => a - b);
// Способ 2: slice() без аргументов возвращает копию
const sorted2 = original.slice().sort((a, b) => a - b);
// Способ 3: Array.from() — создаёт новый массив
const sorted3 = Array.from(original).sort((a, b) => a - b);
console.log(original); // → [3, 1, 4, 1, 5, 9, 2, 6] — не изменился!
console.log(sorted1); // → [1, 1, 2, 3, 4, 5, 6, 9] — отсортированная копия
// Начиная с ES2023 есть Array.prototype.toSorted() — возвращает копию
const sorted4 = original.toSorted((a, b) => a - b);
✨ Хорошая практика: в современном коде чаще всего пишут [...arr].sort(comparator). Это явно показывает: «я создаю копию и сортирую её, оригинал не трогаю». Избегайте неожиданных мутаций!
✅ Итоги урока
sort()без аргументов сортирует как строки — для чисел даёт неверный результат- Компаратор
(a, b) => a - b— сортировка чисел по возрастанию - Компаратор
(a, b) => b - a— сортировка чисел по убыванию - Для строк используйте
a.localeCompare(b, "ru")чтобы правильно учесть язык - Для объектов указывайте в компараторе нужное свойство:
a.price - b.price - sort изменяет исходный массив! Для сортировки без мутации —
[...arr].sort() - Логика компаратора: отрицательное → a перед b, ноль → не меняем, положительное → b перед a
В следующем уроке — деструктуризация массивов и объектов: один из самых удобных синтаксисов современного JavaScript. 🎁
Сортировка с компаратором
6 вопросов