Почему Array.sort() может создать баг далеко от места сортировки!С sort() проблема не в самой сортировке, а в скрытой мутации. Вызов выглядит как обычное получение отсортированного результата, хотя одновременно изменяет сам исходный массив:
const scores = [30, 10, 20];
const sorted = scores.sort(
(a, b) => a - b
);
console.log(scores);
// [10, 20, 30]
sort() сортирует массив непосредственно и возвращает ту же ссылку. Поэтому изменение увидит не только код, работающий с sorted, но и любой код, который уже хранит ссылку на исходный массив:
const scores = [30, 10, 20];
const cached = scores;
scores.sort((a, b) => a - b);
console.log(cached);
// [10, 20, 30]
Особенно неприятно это становится на границе функций. По названию getSortedUsers() можно ожидать получение нового результата, но реализация незаметно меняет массив вызывающего кода:
function getSortedUsers(users) {
return users.sort(
(a, b) => a.age - b.age
);
}
В результате функция имеет побочный эффект, который не очевиден из места её вызова. Это уже может затронуть состояние, кэш, мемоизацию или другой код, использующий тот же массив:
const users = getUsers();
const original = users;
getSortedUsers(users);
console.log(original === users);
// true
Классический способ избежать этого — сначала создать новый массив и сортировать уже его. Такой код работает, но копирование приходится выражать отдельной операцией:
const sorted = [...scores].sort(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
toSorted() делает намерение явным: получить новый массив с другим порядком элементов, не изменяя исходный. При этом возвращается уже другая ссылка:
const sorted = scores.toSorted(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
console.log(scores === sorted);
// false
Важно, что копируется именно массив. Объекты внутри него не клонируются, поэтому обе коллекции по-прежнему содержат ссылки на одни и те же объекты:
const users = [
{ name: "Alex", age: 30 },
{ name: "Bob", age: 20 },
];
const sorted = users.toSorted(
(a, b) => a.age - b.age
);
console.log(
sorted[0] === users[1]
);
// true
Поэтому изменение самого объекта будет видно через обе коллекции:
sorted[0].age = 25;
console.log(users[1].age);
// 25
Похожая логика есть у toReversed() и toSpliced(): они позволяют выполнить соответствующую операцию без изменения исходного массива, в отличие от мутирующих reverse() и splice().
🔥 Поэтому выбор между sort() и toSorted() — это не просто вопрос синтаксиса, это выбор контракта операции: изменить существующий массив или получить новый, сохранив исходный. Чем больше у массива потребителей, тем важнее эта разница.
📣
JS Ready | #практика