Ссылки и иммутабельность
Значение и ссылка, иммутабельные обновления, поверхностная и глубокая копия.
Обновлено
Что значат «значение» и «ссылка»
Когда вы присваиваете значение переменной, JS делает одно из двух, в зависимости от типа:
| По значению | По ссылке | |
|---|---|---|
| Типы | примитивы: string, number, boolean, null, undefined | объекты, массивы, функции |
| Что копируется | само значение | адрес того же значения в памяти |
| Эффект | две независимые копии | два имени для одного и того же объекта |
let a = 1
let b = a
b = 2
a // 1 — независимые копии
const x = { count: 1 }
const y = x // y указывает на тот же объект, что и x
y.count = 2
x.count // 2 — это один и тот же объект!Аналогия: примитивы — как ксерокопия листа; объекты — как ссылка на Google Doc: вы отправляете ссылку, а не сам документ.
Сравнение подчиняется тому же правилу
'a' === 'a' // true — сравниваются значения
{ a: 1 } === { a: 1 } // false — два разных объекта, разные адреса
const o = {}; o === o // true — один и тот же адресЧто такое иммутабельность
Иммутабельно = вы не меняете существующий объект, а создаёте новый с изменением. Противоположность — мутация, изменение на месте.
// мутация
user.age = 31
// иммутабельно
const updated = { ...user, age: 31 }Почему это важно — особенно в React
React решает, перерисовывать ли, сравнивая старое и новое состояние по ссылке (Object.is). Если изменить объект на месте, ссылка та же → React думает, что ничего не изменилось → интерфейс не обновляется.
// ✗ мутация: та же ссылка, перерисовки нет
todos.push(newTodo)
setTodos(todos)
// ✓ новый массив
setTodos([...todos, newTodo])Другие плюсы: можно быстро проверить «изменилось ли?», есть история (undo), нет скрытых эффектов между частями кода, которые делят один объект.
Шпаргалка: иммутабельные обновления
| Операция | Массив | Объект |
|---|---|---|
| добавить | [...arr, x] |
{ ...obj, key: v } |
| удалить | arr.filter(x => x.id !== id) |
const { key, ...rest } = obj |
| изменить один | arr.map(x => x.id === id ? { ...x, done: true } : x) |
{ ...obj, key: v } |
| отсортировать | arr.toSorted(fn) |
— |
| по индексу | arr.with(i, v) |
— |
| вложенное | — | { ...obj, address: { ...obj.address, city } } |
Поверхностная и глубокая копия
| Поверхностная (shallow) | Глубокая (deep) | |
|---|---|---|
| Как | { ...obj }, [...arr], Object.assign |
structuredClone(obj) |
| Что копирует | только первый уровень; вложенные объекты остаются общими | всё, на любой глубине |
| Стоимость | маленькая | больше |
| Когда | обновления состояния (копируете только то, что меняете) | когда действительно нужна независимая копия |
const copy = { ...user }
copy.address.city = 'Самара' // ✗ меняет и user.address — это тот же объект
const deep = structuredClone(user)
deep.address.city = 'Самара' // ✓ user не тронутJSON.parse(JSON.stringify(x)) — старый хак; теряет Date, Map, undefined, функции. Используйте structuredClone.
Object.freeze
Object.freeze(obj) блокирует изменения (только первый уровень). Полезно для констант конфигурации, чтобы ловить случайные мутации.
Коротко
- Примитивы копируются по значению, объекты — по ссылке.
- Иммутабельно = создать новый объект вместо изменения; обязательно для состояния в React.
- Spread копирует только первый уровень;
structuredClone— для полной копии.