Мутабельність та імутабельність
JavaScript мутабельність та імутабельність — глина яку можна змінити та мармурова скульптура яка зафіксована назавжди
В попередніх уроках ми працювали з примітивами, масивами та об'єктами. Ти вже знаєш, що sort() змінює оригінальний масив, а map() — ні. Що const масив можна змінювати, а const число — ні. Але чому так? Час розібратися з однією з найважливіших концепцій JavaScript — мутабельністю.
Два світи: примітиви та посилання
В JavaScript всі дані діляться на два типи за тим, як вони зберігаються в пам'яті:
Примітиви (immutable — незмінні): string, number, boolean, null, undefined
Посилальні типи (mutable — змінні): object, array, function
Ця різниця — корінь багатьох "магічних" поведінок JavaScript.
Примітиви — копіюються за значенням
Коли ти присвоюєш примітив новій змінній, створюється незалежна копія:
let price = 100;
let discountPrice = price;
discountPrice = 80;
console.log(price); // 100 — не змінилось!
console.log(discountPrice); // 80
Це як переписати число з одного блокнота в інший — зміна в другому не впливає на перший.
Те саме з рядками:
let greeting = "Привіт";
let copy = greeting;
copy = "Бувай";
console.log(greeting); // "Привіт" — оригінал не змінився
Примітиви імутабельні (immutable) — їх значення не можна змінити. Операція discountPrice = 80 не змінює число 100, а створює нове число 80 і записує його в змінну.
Об'єкти та масиви — копіюються за посиланням
А ось з об'єктами все інакше. Коли ти присвоюєш об'єкт змінній, в ній зберігається не сам об'єкт, а посилання (адреса) на нього в пам'яті:
const user = { name: "Олексій", age: 25 };
const admin = user; // admin вказує на ТОЙ САМИЙ об'єкт
admin.age = 30;
console.log(user.age); // 30 — змінився "оригінал"!
console.log(admin.age); // 30
Обидві змінні user та admin — це два пульти від одного телевізора. Натиснув на одному — змінилося на обох.
З масивами — те саме:
const original = [1, 2, 3];
const copy = original; // це НЕ копія, це те саме посилання
copy.push(4);
console.log(original); // [1, 2, 3, 4] — "оригінал" теж змінився!
const захищає змінну від перезапису (original = [...] — помилка), але не захищає вміст об'єкта чи масиву від змін. const об'єкт все одно можна мутувати.
Порівняння: значення vs посилання
Примітиви порівнюються за значенням:
console.log(5 === 5); // true
console.log("Київ" === "Київ"); // true
Об'єкти порівнюються за посиланням (чи це один і той самий об'єкт у пам'яті):
const a = { name: "Олексій" };
const b = { name: "Олексій" };
const c = a;
console.log(a === b); // false — різні об'єкти (хоч вміст однаковий)
console.log(a === c); // true — одне й те саме посилання
Це пояснює, чому React порівнює state за посиланням — якщо ти мутуєш об'єкт і передаєш його в setState, посилання не змінюється, і React не бачить зміни.
Мутабельність: що це?
Мутабельність (mutability) — здатність змінюватися після створення.
- Мутабельні (mutable): об'єкти та масиви — можна змінювати їхній вміст
- Імутабельні (immutable): примітиви — значення не можна змінити, лише створити нове
// Мутація об'єкта — змінюємо існуючий
const user = { name: "Олексій" };
user.name = "Марія"; // мутація — той самий об'єкт, інший вміст
// Імутабельне оновлення — створюємо новий
const updated = { ...user, name: "Марія" }; // новий об'єкт
Функції та мутабельність
Коли ти передаєш примітив у функцію — функція отримує копію. Коли передаєш об'єкт чи масив — функція отримує посилання на оригінал:
// Примітив — безпечно
function double(num) {
num = num * 2;
return num;
}
let price = 100;
double(price);
console.log(price); // 100 — не змінився
// Об'єкт — обережно!
function celebrate(user) {
user.age += 1; // мутує оригінальний об'єкт!
}
const person = { name: "Олексій", age: 25 };
celebrate(person);
console.log(person.age); // 26 — оригінал змінився!
Безпечніший варіант — не мутувати аргумент:
function celebrate(user) {
return { ...user, age: user.age + 1 }; // новий об'єкт
}
const person = { name: "Олексій", age: 25 };
const olderPerson = celebrate(person);
console.log(person.age); // 25 — оригінал цілий
console.log(olderPerson.age); // 26 — нова версія
Мутуючі vs немутуючі методи масивів
Тепер ти розумієш, чому одні методи змінюють оригінал, а інші — ні:
| Мутуючі (змінюють оригінал) | Немутуючі (повертають новий) |
|---|---|
push(), pop() | map() |
shift(), unshift() | filter() |
sort() | slice() |
splice() | concat() |
reverse() | [...arr] (spread) |
const numbers = [3, 1, 2];
// Мутуючий sort — змінює оригінал
numbers.sort();
console.log(numbers); // [1, 2, 3] — масив змінено
// Немутуючий підхід — копіюємо перед sort
const sorted = [...numbers].sort((a, b) => b - a);
console.log(numbers); // [1, 2, 3] — оригінал цілий
console.log(sorted); // [3, 2, 1] — нова копія
Сучасний JavaScript (ES2023) додав немутуючі аналоги: toSorted(), toReversed(), toSpliced(). Вони повертають новий масив, не змінюючи оригінал: numbers.toSorted((a, b) => a - b).
Чому імутабельність — це важливо?
- Передбачуваність — якщо функція не мутує вхідні дані, вона не створює побічних ефектів. Баги легше знаходити
- React — порівнює state за посиланням. Мутований об'єкт має те саме посилання — React не побачить зміну і не оновить інтерфейс
- Відлагодження — можна зберігати "знімки" стану (undo/redo, time-travel debugging)
Ти зустрінеш це на практиці в уроках React, де кожне оновлення state має бути імутабельним.
Підсумок
- Примітиви (number, string, boolean) — імутабельні, копіюються за значенням
- Об'єкти та масиви — мутабельні, копіюються за посиланням
- Присвоєння об'єкта іншій змінній не створює копію — обидві змінні вказують на один об'єкт
constне робить об'єкт імутабельним — лише забороняє перезапис змінної- Мутуючі методи (
push,sort,splice) змінюють оригінал; немутуючі (map,filter,slice) створюють новий масив - Імутабельний підхід:
{ ...obj, key: value }для об'єктів,[...arr]для масивів - В React, Redux та сучасному JS — імутабельність є стандартом
Що далі?
Блок JavaScript Basics завершено! Далі — Block 5: JavaScript & DOM, де ми навчимося маніпулювати HTML-сторінками з JavaScript.
Корисні посилання:
- javascript.info: Копіювання об'єктів — посилання та копіювання (українською)
- MDN: Primitive — що таке примітиви
- React.dev: Updating Objects in State — чому імутабельність критична в React
- Change Array by Copy (ES2023) — нові немутуючі методи масивів