Копирование и ссылки

Копирование и ссылки #

Присваивание объекта не клонирует данные — обе переменные указывают на один экземпляр в памяти. Чтобы изменить копию, не затрагивая оригинал, нужно явное копирование: поверхностное для плоских структур или глубокое для вложенных объектов и массивов.


Ссылка и мутация #

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 всего массива или каждого элемента по необходимости.


Практический выбор стратегии #

  1. Передать ссылку намеренно — общее мутабельное состояние (осторожно с побочными эффектами).
  2. Shallow copy — обновить поля верхнего уровня, иммутабельный патч состояния.
  3. Deep copy — снимок для отката, история изменений, отправка копии в воркер без гонок.
  4. Не копировать без нужды — лишние клоны дороже по памяти и 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 с потерей типов и спецзначений.
  • Выбирайте глубину копирования по тому, какие уровни должны изменяться независимо.

См. также #

Часто спрашивают

Почему this «потерялся» в колбэке?
У обычной функции this зависит от способа вызова. Сохраните контекст через bind или используйте стрелочную функцию осознанно. Подробнее