Skip to main content

Копіювання через Object.assign

Object.assign() створює поверхневу копію (shallow copy): він переносить у ціль лише власні перелічувані властивості першого рівня. Це означає, що вкладені об'єкти та масиви копіюються за посиланням, тож оригінал і копія продовжують ділити одні й ті самі внутрішні структури.

Теорія

TL;DR

  • Object.assign() дає shallow copy, а не глибоку копію.
  • Копіюється тільки перший рівень властивостей.
  • Примітиви (string, number, boolean) копіюються за значенням і стають незалежними.
  • Вкладені об'єкти, масиви та функції копіюються за посиланням.
  • Зміна поля всередині вкладеного об'єкта видно і в оригіналі, і в копії.
  • Для незалежної копії використовуйте structuredClone(), JSON.parse(JSON.stringify()) або cloneDeep() з Lodash.

Швидкий приклад

javascript
const user = { name: 'Alice', address: { city: 'Kyiv', zip: 12345 } }; const clone = Object.assign({}, user); clone.address.city = 'Lviv'; console.log(user.address.city); // 'Lviv', а не 'Kyiv'

Чому так:

  • Object.assign() копіює лише перший рівень властивостей;
  • властивість address - це об'єкт, тому копіюється посилання на нього;
  • зміна вкладеного об'єкта (city) відображається в оригіналі.

Як це виглядає в пам'яті

javascript
user.address ────┐ (один і той самий об'єкт) clone.address ────┘

Обидва об'єкти, user і clone, посилаються на один і той самий address. Тому зміна всередині address зачіпає обидва. У самих об'єктах user і clone лежить не структура адреси, а адреса цієї структури в пам'яті, і Object.assign() чесно скопіював саме її.

Але примітивні властивості копіюються нормально

javascript
const user = { name: 'Alice', age: 25 }; const clone = Object.assign({}, user); clone.name = 'Oleh'; console.log(user.name); // 'Alice', незалежно

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

Як уникнути цієї проблеми

Якщо потрібна глибока копія, використовуйте один із варіантів.

structuredClone(), вбудований сучасний спосіб:

javascript
const clone = structuredClone(user); clone.address.city = 'Lviv'; console.log(user.address.city); // 'Kyiv', оригінал не зачеплено

JSON.parse(JSON.stringify()), універсально, але з обмеженнями (втрачаються undefined, функції, Date перетворюється на рядок, циклічні посилання кидають помилку):

javascript
const clone = JSON.parse(JSON.stringify(user));

_.cloneDeep() з Lodash, надійно і сумісно з усіма типами:

javascript
import _ from 'lodash'; const clone = _.cloneDeep(user);

Підсумок: що саме копіює Object.assign

Що копіює Object.assign()Поведінка
Примітивні значення (string, number, boolean)копіюються за значенням
Вкладені об'єкти та масивикопіюються за посиланням
Функціїкопіюються за посиланням
Глибокі структурине копіюються незалежно

Типові помилки

  • Вважати Object.assign({}, obj) повним клоном. Це лише верхній рівень; будь-яка вкладеність лишається спільною.
  • Думати, що spread робить щось інше. { ...obj } має рівно ту саму поверхневу семантику, що й Object.assign({}, obj).
  • Мутувати вкладений об'єкт у стані додатка після «копіювання». У React або Redux це класичне джерело багів: посилання не змінилося, тож компонент не перерендериться, зате оригінал уже зіпсовано.
  • Сліпо застосовувати JSON.parse(JSON.stringify()). Цей трюк мовчки викидає undefined і функції, перетворює Date на рядок і падає на циклічних посиланнях.
  • Забувати, що Object.assign() мутує ціль. Перший аргумент змінюється на місці, тому Object.assign(user, patch) це не копіювання, а оновлення user.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.