Копирование и ссылки #
Присваивание объекта не клонирует данные — обе переменные указывают на один экземпляр в памяти. Чтобы изменить копию, не затрагивая оригинал, нужно явное копирование: поверхностное для плоских структур или глубокое для вложенных объектов и массивов.
Ссылка и мутация #
const cartA = { id: 'cart-7a2', total: 1490 }
const cartB = cartA
cartB.total = 0
cartA.total // 0 — изменился тот же объект
Сравнение объектов через === проверяет идентичность ссылки, а не равенство содержимого:
const left = { sku: 'kb-01' }
const right = { sku: 'kb-01' }
left === right // false — разные объекты
left === left // true
Поверхностное копирование #
Поверхностная копия создаёт новый объект верхнего уровня, но вложенные объекты и массивы остаются общими ссылками.
Spread #
const playlist = {
title: 'Focus Tracks',
meta: { isPublic: true, playCount: 0 },
}
const clone = { ...playlist }
clone.title = 'Deep Work'
clone.meta.playCount = 42
playlist.title // "Focus Tracks" — верхний уровень изолирован
playlist.meta.playCount // 42 — вложенный meta общий
Object.assign #
const base = { currency: 'RUB', items: [] }
const patch = { discount: 10 }
const merged = Object.assign({}, base, patch)
// { currency: 'RUB', items: [], discount: 10 }
Object.assign мутирует первый аргумент-цель; для копии первым передают пустой объект {}. Семантика глубины та же, что у spread — только первый уровень.
| Способ | Новый объект верхнего уровня | Вложенные структуры |
|---|---|---|
= | нет | общие |
{ ...obj } | да | общие |
Object.assign({}, obj) | да | общие |
Когда достаточно shallow copy #
Плоский профиль, DTO без вложенности, иммутабельное обновление одного поля — типичные сценарии для spread:
const profile = {
userId: 'u-104',
displayName: 'Мария К.',
role: 'editor',
}
const promoted = { ...profile, role: 'admin' }
// profile.role остаётся 'editor'
Тот же приём — в основах объектов при обновлении корзины без мутации исходника.
Глубокое копирование #
Глубокая копия рекурсивно дублирует вложенные объекты и массивы, чтобы изменения в копии не протекали в оригинал.
structuredClone #
Встроенный API (современные браузеры и Node.js):
const apiPayload = {
event: 'cart.updated',
data: {
items: [{ sku: 'kb-01', qty: 1 }],
totals: { subtotal: 1490 },
},
}
const snapshot = structuredClone(apiPayload)
snapshot.data.items.push({ sku: 'ms-02', qty: 2 })
snapshot.data.totals.subtotal = 2990
apiPayload.data.items.length // 1 — оригинал не тронут
apiPayload.data.totals.subtotal // 1490
Ограничения structuredClone: не клонирует функции, символы с зарегистрированными глобально описаниями, DOM-узлы и некоторые встроенные типы (Date, Map, Set поддерживаются). Для сериализуемых JSON-подобных деревьев — надёжный выбор.
JSON.parse(JSON.stringify(…)) #
Быстрый обходной путь только для данных, которые целиком проходят через JSON:
const copy = JSON.parse(JSON.stringify(apiPayload))
Минусы: теряются undefined, Symbol, функции, Date превращается в строку, циклические ссылки вызывают ошибку. Для ответов REST API без экзотики часто достаточно.
Копирование массивов объектов #
Элементы массива — те же ссылки, что и в исходнике:
const lineItems = [
{ sku: 'kb-01', price: 1490 },
{ sku: 'ms-02', price: 890 },
]
const itemsCopy = [...lineItems]
itemsCopy[0].price = 0
lineItems[0].price // 0 — объекты внутри общие
Для независимых элементов комбинируйте map и spread:
const independentItems = lineItems.map((item) => ({ ...item }))
При глубокой вложенности — structuredClone всего массива или каждого элемента по необходимости.
Практический выбор стратегии #
- Передать ссылку намеренно — общее мутабельное состояние (осторожно с побочными эффектами).
- Shallow copy — обновить поля верхнего уровня, иммутабельный патч состояния.
- Deep copy — снимок для отката, история изменений, отправка копии в воркер без гонок.
- Не копировать без нужды — лишние клоны дороже по памяти и CPU.
function applyDiscount(cart, percent) {
return {
...cart,
totals: {
...cart.totals,
discount: (cart.totals.subtotal * percent) / 100,
},
}
}
Ручной spread по уровням — контролируемая «средняя глубина» без полного deep clone.
Итог #
- Присваивание объекта копирует ссылку, не содержимое.
- Spread и
Object.assignдают поверхностную копию — вложенности остаются общими. structuredClone— стандартный способ глубокого клонирования сериализуемых структур.JSON.parse(JSON.stringify())— узкий fallback с потерей типов и спецзначений.- Выбирайте глубину копирования по тому, какие уровни должны изменяться независимо.