Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Методи масивів з мутацією та без мутації в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Мутуючі методи масивів** змінюють оригінальний масив. Немутуючі повертають нові дані, не змінюючи оригінал. ```js const arr = [3, 1, 2]; arr.sort(); // мутує arr - [1, 2, 3] const copy = [...arr].sort(); // безпечно - arr не змінився ``` **Ключове:** У React і Redux завжди використовуй немутуючі методи (`map`, `filter`, `slice`, spread) - мутація обходить виявлення змін у React.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Мутуючі методи масивів** змінюють оригінальний масив безпосередньо. Немутуючі повертають нові дані, не чіпаючи оригінал. ## Теорія ### TL;DR - Мутуючі: `push()`, `pop()`, `splice()`, `sort()`, `reverse()` - масив змінюється після виклику - Немутуючі: `map()`, `filter()`, `slice()`, `concat()`, `find()` - оригінал залишається незмінним - Аналогія: мутація - це правка документа напряму; немутуючий підхід - спочатку дублікат, потім правка копії - React і Redux вимагають немутуючих методів - мутація обходить виявлення змін повністю - Швидке рішення: `[...arr].sort()` дає відсортовану копію без зміни `arr` ### Швидкий приклад ```js const original = [3, 1, 2]; // sort() мутує - оригінал змінюється original.sort(); console.log(original); // [1, 2, 3] - змінився // spread + sort - оригінал залишається const nums = [3, 1, 2]; const sorted = [...nums].sort(); console.log(nums); // [3, 1, 2] - не змінився console.log(sorted); // [1, 2, 3] - новий масив ``` `sort()` повертає посилання на той самий масив, який він змінив, а не новий. Це несподіванка для тих, хто очікує поведінки як у `map()`. ### Ключова різниця Мутуючі методи змінюють пам'ять масиву напряму. Немутуючі виділяють нову пам'ять, обчислюють значення і повертають результат. Якщо дві змінні вказують на один масив, одна мутація зачіпає обидві. Виявлення змін у React порівнює посилання (`arr !== prevArr`), тому мутація масиву змушує React думати, що нічого не змінилось, і пропустити повторний рендер. ### Коли використовувати - **Мутуючі методи:** коли масив належить тільки тобі і жоден інший код не тримає посилання на нього; у щільних циклах, де виділення нових масивів помітно впливає на швидкість - **Немутуючі методи:** стандартний вибір в оновленнях стану React, редюсерах Redux і будь-якій функції, яка отримує масив як параметр - **Змішаний підхід:** мутуй всередині функції, яка сама створила дані, потім повертай результат як нове значення ### Таблиця порівняння | Метод | Тип | Повертає | Змінює оригінал | Де використовувати | |-------|-----|---------|-----------------|-------------------| | `push()` | Мутуючий | Нову довжину | Так | Додавання до власного масиву | | `concat()` | Немутуючий | Новий масив | Ні | Безпечне об'єднання масивів | | `splice()` | Мутуючий | Видалені елементи | Так | Видалення/вставка за індексом | | `slice()` | Немутуючий | Новий масив | Ні | Безпечне отримання частини | | `sort()` | Мутуючий | Той самий масив | Так | Коли мутація прийнятна | | `toSorted()` | Немутуючий | Новий масив | Ні | Сортування без побічних ефектів (ES2023) | | `reverse()` | Мутуючий | Той самий масив | Так | Коли мутація прийнятна | | `toReversed()` | Немутуючий | Новий масив | Ні | Реверс без побічних ефектів (ES2023) | | `map()` | Немутуючий | Новий масив | Ні | Трансформація даних (React, Redux) | | `filter()` | Немутуючий | Новий масив | Ні | Фільтрація даних (React, Redux) | | `find()` | Немутуючий | Один елемент | Ні | Пошук без змін | | `fill()` | Мутуючий | Той самий масив | Так | Ініціалізація значень масиву | ### Типові помилки **Помилка 1: Мутація стану в React** ```js // Неправильно - мутація, React пропускає ре-рендер const handleAdd = () => { state.items.push(newItem); setState(state); // те саме посилання - React не бачить змін }; // Правильно - новий масив, React виявляє зміни const handleAdd = () => { setState([...state.items, newItem]); }; ``` React порівнює `prevState === newState`. Мутація зберігає те саме посилання, тому перевірка проходить і компонент залишається застарілим. В оновленнях стану завжди створюй новий масив. **Помилка 2: Очікування, що splice() поверне змінений масив** ```js const arr = [1, 2, 3]; const result = arr.splice(1, 1); console.log(result); // [2] - ВИДАЛЕНИЙ елемент, не [1, 3] console.log(arr); // [1, 3] - змінений масив, але не в result ``` Повернене значення `splice()` дивує майже всіх, хто стикається з ним вперше. Ти очікуєш змінений масив, а отримуєш видалені елементи. Використовуй сам масив після виклику splice, або переходь на `filter()` якщо потрібна немутуюча поведінка. **Помилка 3: Сортування чисел без функції порівняння** ```js const numbers = [10, 5, 40, 25]; numbers.sort(); console.log(numbers); // [10, 25, 40, 5] - неправильно, сортування як рядки numbers.sort((a, b) => a - b); console.log(numbers); // [5, 10, 25, 40] - правильно ``` Без компаратора `sort()` перетворює елементи на рядки і порівнює їх коди символів. "40" стоїть перед "5" за алфавітом. Для чисел завжди передавай `(a, b) => a - b`. **Помилка 4: Мутація масиву під час ітерації** ```js // Неправильно - пропускає елементи непередбачувано const arr = [1, 2, 3, 4]; arr.forEach(item => { if (item === 2) arr.splice(arr.indexOf(item), 1); }); // Правильно - filter створює новий масив const filtered = [1, 2, 3, 4].filter(item => item !== 2); console.log(filtered); // [1, 3, 4] ``` Splice під час ітерації зміщує індекси і призводить до пропуску елементів. Використовуй `filter()` замість нього. ### Де зустрічається - **React:** `map()`, `filter()`, `concat()`, spread-оператор для всіх оновлень стану - **Redux:** редюсери повертають новий стан - `filter()`, `map()`, spread-синтаксис; ніколи `push()` або `splice()` - **Vue.js:** немутуючі методи підтримують реактивну систему у робочому стані - **Node.js/Express:** мутуючі методи нормально використовувати всередині обробників маршрутів, які самостійно керують даними - **ES2023:** `toSorted()`, `toReversed()`, `toSpliced()` - вбудовані немутуючі альтернативи, більше не потрібно spread перед сортуванням ### Питання на співбесіді **Q:** Чому React вимагає немутуючих методів масивів? **A:** React порівнює посилання (`prevState === newState`) для виявлення змін. Мутація масиву зберігає те саме посилання, тому перевірка повертає `true` і React пропускає ре-рендер. Новий масив завжди має інше посилання. **Q:** Яка різниця в продуктивності між мутуючими і немутуючими методами? **A:** Немутуючі виділяють нову пам'ять і копіюють дані. Для масивів до 1000 елементів різниця незначна. У щільних циклах з великими масивами мутація може мати значення. Для більшості коду незмінність вартує цього невеликого накладного розходу. **Q:** Як зробити sort() немутуючим без ES2023? **A:** Спочатку створи копію: `[...arr].sort()` або `arr.slice().sort()`. Обидва варіанти дають поверхневу копію, тому сортування копії не змінює оригінал. **Q:** (Senior) Чому `map()` завжди повертає новий масив, а `sort()` змінює оригінал? **A:** `sort()` переставляє елементи на місці і повторно використовує існуючу пам'ять - це рішення заради продуктивності в оригінальній специфікації. `map()` виробляє трансформовані значення, і виділення нової пам'яті тут неминуче, бо самі значення змінюються. Різниця пов'язана з алгоритмічною необхідністю не менше, ніж з дизайном. ## Приклади ### Оновлення стану React із сортуванням ```js function TodoList({ todos, setTodos }) { // Неправильно - мутує todos, React не ре-рендериться const handleSortWrong = () => { todos.sort((a, b) => a.priority - b.priority); setTodos(todos); // те саме посилання, без ре-рендеру }; // Правильно - новий масив, React виявляє зміни const handleSort = () => { const sorted = [...todos].sort((a, b) => a.priority - b.priority); setTodos(sorted); }; return <ul>{todos.map(t => <li key={t.id}>{t.text}</li>)}</ul>; } ``` `[...todos]` створює поверхневу копію масиву. Сортування цієї копії дає нове посилання, яке React бачить як зміну і ре-рендерить компонент. ### splice() проти slice() - схожі назви, протилежна поведінка ```js const arr = [1, 2, 3, 4, 5]; // splice() - мутує, повертає видалені елементи const removed = arr.splice(1, 2); console.log(removed); // [2, 3] - що видалили console.log(arr); // [1, 4, 5] - що залишилось (arr змінився) // slice() - без мутації, повертає те що вибрали const arr2 = [1, 2, 3, 4, 5]; const kept = arr2.slice(1, 3); console.log(kept); // [2, 3] - що вибрали console.log(arr2); // [1, 2, 3, 4, 5] - не змінився ``` `splice()` і `slice()` виглядають схоже, але працюють протилежно. `splice()` мутує і повертає видалене. `slice()` повертає вибране і не чіпає оригінал. ### Підводний камінь сортування чисел ```js const prices = [100, 25, 1000, 50]; // Сортування за замовчуванням обробляє числа як рядки prices.sort(); console.log(prices); // [100, 1000, 25, 50] - алфавітний порядок // Немутуюче сортування з числовим компаратором const sortedPrices = [...prices].sort((a, b) => a - b); console.log(sortedPrices); // [25, 50, 100, 1000] - правильно console.log(prices); // [100, 1000, 25, 50] - оригінал не змінився ``` Сортування за замовчуванням порівнює рядки. "1000" стоїть перед "25" за алфавітом. Для числового сортування завжди передавай компаратор. Патерн spread + sort зберігає оригінал недоторканим.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.