Пошук уроків, статей та іншого контенту
Освоїте reverse, toReversed, toSorted і toSpliced для керування порядком та безпечної роботи без мутацій.
Масив у JavaScript є змінюваним об’єктом. Це означає, що метод може:
змінити наявний масив;
повернути посилання на цей самий масив;
створити новий масив, залишивши початковий без змін.
Різниця особливо важлива в:
Redux та інших підходах із незмінним станом;
React, де зміна посилання допомагає виявити оновлення;
функціональному програмуванні;
кешуванні та мемоізації;
коді, де один масив використовується в кількох частинах програми.
const original = [3, 1, 2];
const alias = original;
original.reverse();
console.log(original); // [2, 1, 3]
console.log(alias); // [2, 1, 3]
console.log(original === alias); // truealias не є копією. Це друге посилання на той самий масив, тому мутація через original помітна і через alias.
reverse і toReversedreverseМетод reverse() змінює порядок елементів у поточному масиві та повертає це саме посилання.
const numbers = [1, 2, 3, 4];
const result = numbers.reverse();
console.log(numbers); // [4, 3, 2, 1]
console.log(result); // [4, 3, 2, 1]
console.log(result === numbers); // trueПовернене значення reverse() не є новою колекцією. Це той самий масив.
toReversedМетод toReversed() створює новий масив у зворотному порядку й не змінює початковий масив.
const numbers = [1, 2, 3, 4];
const reversed = numbers.toReversed();
console.log(numbers); // [1, 2, 3, 4]
console.log(reversed); // [4, 3, 2, 1]
console.log(reversed === numbers); // falseЦей метод належить до новішої групи немутуючих методів масивів і доступний у середовищах із підтримкою сучасного стандарту JavaScript.
Використовуйте reverse(), якщо:
масив належить поточній операції;
зміна масиву є очікуваною;
немає інших споживачів цього самого посилання.
Використовуйте toReversed(), якщо:
потрібно зберегти початкові дані;
масив є частиною стану;
код має бути безпечним щодо спільних посилань;
потрібно отримати нове значення в одному виразі.
До появи toReversed() немутуючий варіант зазвичай записували так:
const reversed = [...numbers].reverse();або:
const reversed = numbers.slice().reverse();Обидва варіанти спочатку створюють поверхневу копію, після чого reverse() змінює вже цю копію.
sort і toSortedsortМетод sort() сортує масив на місці та повертає те саме посилання.
Без функції порівняння елементи перетворюються на рядки й порівнюються лексикографічно:
const values = [10, 2, 30, 4];
values.sort();
console.log(values); // [10, 10?]У цьому прикладі правильний результат:
const values = [10, 2, 30, 4];
values.sort();
console.log(values); // [10, 2, 30, 4]Порядок може здатися несподіваним: як рядки, значення порівнюються так:
"10" < "2" < "30" < "4"Для числового сортування потрібен компаратор:
const values = [10, 2, 30, 4];
values.sort((a, b) => a - b);
console.log(values); // [2, 4, 10, 30]Для спадного порядку:
const values = [10, 2, 30, 4];
values.sort((a, b) => b - a);
console.log(values); // [30, 10, 4, 2]toSortedМетод toSorted() має такі самі правила сортування, як sort(), але повертає новий масив і не змінює початковий.
const values = [10, 2, 30, 4];
const sorted = values.toSorted((a, b) => a - b);
console.log(values); // [10, 2, 30, 4]
console.log(sorted); // [2, 4, 10, 30]Це еквівалентно такому коду:
const sorted = [...values].sort((a, b) => a - b);І sort(), і toSorted() сортують самі елементи масиву, але не копіюють об’єкти всередині нього.
const users = [
{ name: "Olena", score: 82 },
{ name: "Taras", score: 95 },
{ name: "Ihor", score: 74 }
];
const byScore = users.toSorted((a, b) => b.score - a.score);
console.log(byScore);
// [
// { name: "Taras", score: 95 },
// { name: "Olena", score: 82 },
// { name: "Ihor", score: 74 }
// ]
console.log(users);
// Початковий порядок не змінивсяАле об’єкти залишаються спільними:
byScore[0].score = 100;
console.log(users[1].score); // 100toSorted() створює новий масив, але робить лише поверхневу копію. Для незалежного копіювання вкладених об’єктів потрібна окрема стратегія копіювання.
Сучасний JavaScript гарантує стабільне сортування: якщо компаратор вважає два елементи рівними, їхній початковий взаємний порядок зберігається.
const tasks = [
{ title: "Підготувати дані", priority: 1 },
{ title: "Написати тести", priority: 2 },
{ title: "Оновити документацію", priority: 1 }
];
const sorted = tasks.toSorted((a, b) => a.priority - b.priority);
console.log(sorted.map((task) => task.title));
// ["Підготувати дані", "Оновити документацію", "Написати тести"]Дві задачі з пріоритетом 1 залишилися в тому самому порядку, у якому були в початковому масиві.
splice і toSplicedspliceМетод splice() може:
видалити елементи;
вставити елементи;
замінити елементи.
Він змінює початковий масив, а повертає масив видалених елементів.
Сигнатура:
array.splice(start, deleteCount, item1, item2, ...)Приклад видалення:
const languages = ["JavaScript", "Python", "Ruby", "Go"];
const removed = languages.splice(1, 2);
console.log(languages); // ["JavaScript", "Go"]
console.log(removed); // ["Python", "Ruby"]Приклад вставки без видалення:
const languages = ["JavaScript", "Go"];
const removed = languages.splice(1, 0, "Python", "Ruby");
console.log(languages); // ["JavaScript", "Python", "Ruby", "Go"]
console.log(removed); // []Приклад заміни:
const languages = ["JavaScript", "Python", "Go"];
const removed = languages.splice(1, 1, "TypeScript");
console.log(languages); // ["JavaScript", "TypeScript", "Go"]
console.log(removed); // ["Python"]toSplicedМетод toSpliced() виконує аналогічну операцію, але не змінює початковий масив. Він повертає новий результуючий масив, а не масив видалених елементів.
const languages = ["JavaScript", "Python", "Go"];
const updated = languages.toSpliced(1, 1, "TypeScript");
console.log(languages); // ["JavaScript", "Python", "Go"]
console.log(updated); // ["JavaScript", "TypeScript", "Go"]Це важлива відмінність від splice():
const values = [1, 2, 3];
const result = values.toSpliced(1, 1);
console.log(result); // [1, 3]У splice() результатом був би масив видалених елементів:
const values = [1, 2, 3];
const removed = values.splice(1, 1);
console.log(removed); // [2]
console.log(values); // [1, 3]toSplicedВидалити один елемент:
const items = ["a", "b", "c"];
const withoutB = items.toSpliced(1, 1);
console.log(items); // ["a", "b", "c"]
console.log(withoutB); // ["a", "c"]Додати елемент:
const items = ["a", "c"];
const withB = items.toSpliced(1, 0, "b");
console.log(withB); // ["a", "b", "c"]Замінити діапазон:
const items = ["a", "b", "c", "d"];
const replaced = items.toSpliced(1, 2, "x", "y", "z");
console.log(replaced); // ["a", "x", "y", "z", "d"]| Мутуючий метод | Немутуючий метод | Що робить | |---|---|---| | reverse() | toReversed() | змінює або повертає масив у зворотному порядку | | sort() | toSorted() | сортує масив | | splice() | toSpliced() | видаляє, вставляє або замінює елементи |
Практичне правило:
старий метод змінює масив;
метод із префіксом to створює новий масив.
Це правило працює для цієї групи методів, але не означає, що всі методи JavaScript із префіксом to мають однакову семантику поза контекстом масивів.
Немутуючі методи створюють новий масив, але не глибоку копію його елементів.
const source = [
{ id: 1, tags: ["js"] },
{ id: 2, tags: ["web"] }
];
const reversed = source.toReversed();
console.log(reversed === source); // false
console.log(reversed[0] === source[1]); // trueЗміна структури масиву не впливає на source:
const copied = source.toSpliced(0, 1);
console.log(copied); // другий об’єкт у новому масиві
console.log(source); // обидва початкові об’єктиА зміна вкладеного об’єкта впливає:
copied[0].tags.push("frontend");
console.log(source[1].tags); // ["web", "frontend"]Тому потрібно розрізняти:
незмінність масиву — не змінюється структура масиву;
незмінність елементів — не змінюються об’єкти всередині;
глибоку незмінність — не змінюється жоден вкладений рівень.
const initialTasks = [
{ id: 1, title: "Налаштувати CI", priority: 2, completed: false },
{ id: 2, title: "Написати тести", priority: 1, completed: false },
{ id: 3, title: "Оновити README", priority: 3, completed: true }
];
// Новий порядок без зміни initialTasks
const newestFirst = initialTasks.toReversed();
// Новий масив, відсортований за пріоритетом
const byPriority = initialTasks.toSorted(
(a, b) => a.priority - b.priority
);
// Видалення виконаних задач без зміни initialTasks
const pendingTasks = initialTasks.toSpliced(
2,
1
);
// Оновлення поля об'єкта потребує копіювання самого об'єкта
const completedTask = initialTasks.map((task) =>
task.id === 2
? { ...task, completed: true }
: task
);
console.log(initialTasks);
// Початковий масив не змінився
console.log(newestFirst.map((task) => task.id));
// [3, 2, 1]
console.log(byPriority.map((task) => task.id));
// [2, 1, 3]
console.log(pendingTasks.map((task) => task.id));
// [1, 2]
console.log(completedTask);
// Об'єкт із id 2 має completed: trueНемутуючі методи добре комбінуються з map(), filter() і spread-синтаксисом. Але вони не скасовують потребу правильно копіювати вкладені об’єкти.
Розглянемо функцію оновлення стану. Мутуючий варіант може змінити старий стан:
function reverseTasks(state) {
state.tasks.reverse();
return state;
}У цьому випадку:
змінюється попередній стан;
повертається те саме посилання на об’єкт;
системі, яка порівнює посилання, може бути складно виявити оновлення.
Немутуючий варіант:
function reverseTasks(state) {
return {
...state,
tasks: state.tasks.toReversed()
};
}Тут створюються:
новий об’єкт стану;
новий масив tasks;
незмінений попередній стан.
Для сортування:
function sortTasksByPriority(state) {
return {
...state,
tasks: state.tasks.toSorted(
(a, b) => a.priority - b.priority
)
};
}Для видалення елемента:
function removeTask(state, index) {
return {
...state,
tasks: state.tasks.toSpliced(index, 1)
};
}Object.freezeМутуючі методи не можуть працювати із замороженим масивом:
"use strict";
const values = Object.freeze([3, 1, 2]);
// TypeError: reverse намагається змінити заморожений масив
// values.reverse();
const reversed = values.toReversed();
console.log(values); // [3, 1, 2]
console.log(reversed); // [2, 1, 3]toReversed(), toSorted() і toSpliced() не змінюють сам вхідний масив, тому підходять для роботи з замороженими структурами. Однак вони не роблять елементи глибоко незмінними.
Масив може містити пропуски, а не явні значення undefined:
const sparse = [];
sparse[2] = "value";
console.log(sparse.length); // 3
console.log(0 in sparse); // false
console.log(2 in sparse); // trueСучасні немутуючі методи toReversed(), toSorted() і toSpliced() створюють звичайний новий масив. Пропуски під час таких операцій можуть перетворитися на явні undefined, тому для розріджених масивів не варто покладатися на збереження відсутніх індексів.
У більшості прикладних задач краще використовувати щільні масиви, де кожна позиція містить явне значення.
toReversed(), toSorted() і toSpliced() належать до сучасних можливостей JavaScript. Перед використанням у проєкті потрібно переконатися, що цільові браузери або версія Node.js їх підтримують.
Якщо середовище не має цих методів, можна тимчасово використовувати еквівалент через копіювання:
const reversed = [...items].reverse();
const sorted = [...items].sort(compare);
const spliced = [...items];
spliced.splice(start, deleteCount, ...inserted);Такий код зберігає немутуючу поведінку для самого початкового масиву, хоча для toSpliced() він має інший стиль запису.
sort() без компаратора для чиселconst numbers = [100, 20, 3];
numbers.sort();
console.log(numbers); // [100, 20, 3]Для чисел використовуйте:
const sorted = numbers.toSorted((a, b) => a - b);toSorted() глибоко копіює даніconst result = users.toSorted(compare);
result[0].name = "Нове ім'я";Об’єкт користувача може бути тим самим об’єктом, що і в users. Новим є масив, а не кожен його елемент.
toSpliced() повертає видалені елементиconst result = items.toSpliced(1, 1);
console.log(result); // результуючий масив, а не видалені елементиДля отримання видалених елементів використовуйте splice() на копії або виконайте окреме обчислення:
const removed = items.slice(1, 2);
const remaining = items.toSpliced(1, 1);const sorted = items.toSorted(compare);
sorted.reverse();У цьому прикладі початковий items не змінюється, але sorted змінюється. Якщо весь ланцюжок має бути немутуючим, використовуйте:
const result = items.toSorted(compare).toReversed();function prepare(items) {
items.reverse();
return items;
}Функція неочевидно змінює аргумент. Безпечніший варіант:
function prepare(items) {
return items.toReversed();
}toReversed() до іншої метиtoReversed() змінює порядок елементів, але не означає «повернути копію». Для простої копії використовуйте:
const copy = [...items];reverse() змінює початковий масив, а toReversed() створює новий.
sort() сортує на місці, а toSorted() повертає відсортовану копію.
Для чисел у sort() і toSorted() потрібно передавати компаратор.
splice() змінює масив і повертає видалені елементи.
toSpliced() не змінює початковий масив і повертає новий результуючий масив.
Немутуючі методи виконують лише поверхневе копіювання.
Копіювання масиву не копіює вкладені об’єкти.
Для стану застосунку немутуючі методи зменшують ризик прихованих змін і спрощують порівняння посилань.
Якщо середовище не підтримує toReversed(), toSorted() або toSpliced(), можна спочатку створити копію, а потім застосувати старий мутуючий метод.