Вы отслеживаете баг через три файла только для того, чтобы обнаружить, что безобидная вспомогательная функция переименовала пользователя. Вы не трогали оригинал. Или вам так казалось. В 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 все еще хранит свою исходную строку. Такое поведение называется копированием по значению (copy by value). Оно работает идентично для чисел, булевых значений, символов и всей остальной семьи примитивов. Вы можете передавать их в функции, переприсваивать или возвращать, не беспокоясь о побочных эффектах для соседних переменных.
Объекты: общие адреса
Объекты устроены иначе. Объект — это составной контейнер, который объединяет несколько фрагментов данных. В эту категорию входят обычные объекты, массивы, функции, даты и все остальные не примитивные типы. Поскольку эти структуры могут быть большими и вложенными, JavaScript не хранит весь объект внутри переменной. Вместо этого переменная хранит ссылку — по сути, адрес в памяти, указывающий на реальные данные, хранящиеся в другом месте.
Когда вы присваиваете объект новой переменной, движок копирует адрес, а не сам объект. Теперь две переменные указывают на один и тот же «дом».
const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";
console.log(user.name); // "Ali"
Изменение copy.name также изменило user.name, потому что оба имени ведут к одному и тому же базовому объекту. Это копирование по ссылке (copy by reference). Тот же сюрприз происходит и с массивами:
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 гарантирует неизменяемость (immutability). Это не так. Он лишь предотвращает переприсваивание ссылки. Если вы хотите «запечатать» содержимое, вы должны копировать его намеренно.
Где живут настоящие баги
Ошибки, связанные со ссылками, редко проявляются как явные переприсваивания переменных. Они прячутся внутри вызовов функций.
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, где обновления состояния зависят