Ви відстежуєте баг у трьох файлах лише для того, щоб виявити, що невинна допоміжна функція змінила ім'я користувача. Ви ніколи не чіпали оригінал. Або принаймні вам так здавалося. У JavaScript присвоєння однієї змінної іншій не завжди означає те, що ви очікуєте. Мова розділяє дані на дві стратегії зберігання, і саме забуття того, яку з них ви використовуєте, дозволяє тихим мутаціям проникати в робочий код.

Щоб запобігти цьому, вам потрібно бачити різницю між примітивними значеннями та об'єктами саме так, як це робить рушій.

Примітиви: справжні копії

Примітив — це одиничне, неподільне дане. Його неможливо розкласти на менші значення JavaScript. Мова визначає сім примітивних типів: String, Number, Boolean, Undefined, Null, Symbol та BigInt.

Оскільки примітиви є атомарними, вони зазвичай зберігаються безпосередньо всередині прив'язки змінної. Коли ви копіюєте одну примітивну змінну в іншу, рушій дублює фактичні дані. Кожна змінна отримує власне незалежне місце в пам'яті.

let a = "Alina";
let b = a;
b = "Ali";

console.log(a); // "Alina"
console.log(b); // "Ali"

Тут a залишається недоторканою. Переприсвоєння b створило зовсім нове значення і спрямувало b на нього, тоді як a все ще зберігає свій оригінальний рядок. Така поведінка називається копіюванням за значенням. Це працює ідентично для чисел, булевих значень, символів та всієї іншої родини примітивів. Ви можете передавати їх у функції, переприсвоювати або повертати, не турбуючись про побічні ефекти для сусідніх змінних.

Об'єкти: спільні адреси

Об'єкти працюють інакше. Об'єкт — це складений контейнер, який групує кілька частин даних разом. Ця категорія включає звичайні об'єкти, масиви, функції, дати та всі інші не-примітивні типи. Оскільки ці структури можуть бути великими та вкладеними, JavaScript не зберігає весь об'єкт всередині змінної. Замість цього змінна містить посилання — по суті, адресу пам'яті, що вказує на реальні дані, які зберігаються в іншому місці.

Коли ви присвоюєте об'єкт новій змінній, рушій копіює адресу, а не сам об'єкт. Тепер дві змінні вказують на один і той самий будинок.

const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";

console.log(user.name); // "Ali"

Зміна copy.name також змінила user.name, тому що обидва імені посилаються на один і той самий базовий об'єкт. Це копіювання за посиланням. Такий самий сюрприз трапляється з масивами:

const scores = [82, 91, 74];
const backup = scores;
backup.push(88);

console.log(scores); // [82, 91, 74, 88]

У пам'яті існує лише один масив. scores та backup — це лише дві таблички, що вказують на нього.

Ключове слово const додає ще один рівень плутанини. Оголошення об'єкта за допомогою const фіксує прив'язку змінної так, що ви не можете спрямувати її на нову адресу. Це не фіксує сам об'єкт.

const settings = { theme: "dark" };
settings.theme = "light";        // Works perfectly.
settings = { theme: "dark" };    // TypeError

Розробники часто очікують, що const гарантує незмінність. Це не так. Він лише запобігає переприсвоєнню посилання. Якщо ви хочете, щоб вміст був захищений від змін, ви повинні копіювати його навмисно.

Де ховаються справжні баги

Баги, пов'язані з посиланнями, рідко проявляються як очевидні переприсвоєння змінних. Вони ховаються всередині викликів функцій.

function addTimestamp(record) {
  record.timestamp = Date.now();
  return record;
}

const original = { id: 1 };
addTimestamp(original);

console.log(original.timestamp); // A number now exists here. Oops.

Параметр record отримав копію посилання. Будь-яка мутація властивості всередині функції записувалася безпосередньо в об'єкт викликаючої сторони. Функція виглядала як просте перетворення, проте вона спричинила витік стану за межі області видимості.

Цей патерн особливо болючий у UI-фреймворках, таких як React, де оновлення стану залежать