Вивчай

Мутабельність та імутабельність

JavaScript мутабельність та імутабельність — глина яку можна змінити та мармурова скульптура яка зафіксована назавжди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).


Чому імутабельність — це важливо?

  1. Передбачуваність — якщо функція не мутує вхідні дані, вона не створює побічних ефектів. Баги легше знаходити
  2. React — порівнює state за посиланням. Мутований об'єкт має те саме посилання — React не побачить зміну і не оновить інтерфейс
  3. Відлагодження — можна зберігати "знімки" стану (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.

Інфо

Корисні посилання: