Пошук уроків, статей та іншого контенту
Як правильно сортувати масиви, чому пряма мутація масивів і об'єктів небезпечна та як її уникати.
На відміну від map і filter, sort змінює масив, до якого його викликали, а не створює новий — цим він відрізняється від решти методів цього модуля, і про це легко забути:
const numbers = [3, 1, 2];
const sorted = numbers.sort();
sorted; // [1, 2, 3]
numbers; // [1, 2, 3] — оригінал теж змінився! sorted — те саме посиланняНайпоширеніша пастка: без функції порівняння sort перетворює елементи на рядки й порівнює їх лексикографічно, а не як числа:
[10, 1, 2].sort(); // [1, 10, 2] — НЕ [1, 2, 10]!
// "10" лексикографічно менше за "2", бо порівнюється посимвольно: "1" < "2"Для чисел завжди передавайте функцію порівняння — інакше результат буде неправильним для будь-якого масиву з числами, більшими за 9.
Функція приймає два елементи й повертає число: від'ємне, якщо перший має йти раніше; додатне — якщо пізніше; нуль — якщо порядок байдужий:
[10, 1, 2].sort((a, b) => a - b); // [1, 2, 10] — за зростанням
[10, 1, 2].sort((a, b) => b - a); // [10, 2, 1] — за спаданнямТой самий принцип працює для сортування об'єктів за полем:
const users = [{ name: "Ірина", age: 30 }, { name: "Олег", age: 22 }];
users.sort((a, b) => a.age - b.age); // за зростанням вікуМутація (зміна структури «на місці») здається зручнішою — не потрібно створювати новий масив чи об'єкт. Проблема в тому, що коли той самий масив чи об'єкт використовується в кількох місцях коду (а це звичайна ситуація), мутація в одному місці непомітно змінює дані всюди, де на них є посилання. Це джерело важко відстежуваних багів, особливо в застосунках, де стан відображається на екрані (детальніше про це — коли дійдемо до React), і саме тому в сучасному JavaScript перевагу віддають операціям, що повертають новий результат, не чіпаючи оригінал.
Найпростіший спосіб — скопіювати масив через spread перед сортуванням. Сучасні рушії JavaScript (ES2023+) також мають toSorted() — незмінну версію sort «з коробки»:
const numbers = [3, 1, 2];
const sorted = [...numbers].sort((a, b) => a - b);
numbers; // [3, 1, 2] — не змінився
// Або, якщо toSorted підтримується середовищем:
const sortedAlt = numbers.toSorted((a, b) => a - b);Ці три операції разом покривають переважну більшість типових змін масиву без мутації оригіналу:
const items = ["A", "B", "C"];
// Додати елемент
const withNew = [...items, "D"]; // ["A", "B", "C", "D"]
// Видалити елемент за значенням
const withoutB = items.filter((item) => item !== "B"); // ["A", "C"]
// Оновити елемент за індексом
const updated = items.map((item, index) => (index === 1 ? "B2" : item)); // ["A", "B2", "C"]Spread (з попереднього уроку) — основний інструмент для оновлення об'єкта без мутації:
const task = { id: 1, text: "Купити хліб", done: false };
const completed = { ...task, done: true }; // новий об'єкт, task не змінивсяТой самий підхід, що використовує адмін-панель fullstack.org.ua для роботи зі списками — жодна операція не мутує вихідний масив:
function toggleTask(tasks, taskId) {
return tasks.map((task) =>
task.id === taskId ? { ...task, done: !task.done } : task,
);
}
function removeTask(tasks, taskId) {
return tasks.filter((task) => task.id !== taskId);
}Виклик sort() без функції порівняння для масиву чисел.
Забуття, що sort, reverse, push, pop, splice мутують оригінальний масив — на відміну від map, filter, slice.
Мутація об'єкта чи масиву, який передали як аргумент функції, замість повернення нового — викликач не завжди очікує, що його дані зміняться.
sort сортує на місці й за замовчуванням порівнює як рядки — завжди передавайте функцію порівняння для чисел. Мутація («на місці») зручна, але небезпечна, коли структура даних використовується в кількох місцях — spread, map, filter і toSorted дозволяють отримати змінений результат, не займаючи оригінал. Це завершує модуль про об'єкти й масиви — далі зазирнемо під капот самої мови.
Спробуйте самостійно
Напишіть функцію sortByName(users), яка повертає новий масив користувачів, відсортований за полем name (за алфавітом), не змінюючи переданий масив users.