Копіювання через Object.assign
Object.assign() створює поверхневу копію (shallow copy): він переносить у ціль лише власні перелічувані властивості першого рівня. Це означає, що вкладені об'єкти та масиви копіюються за посиланням, тож оригінал і копія продовжують ділити одні й ті самі внутрішні структури.
Теорія
TL;DR
Object.assign()дає shallow copy, а не глибоку копію.- Копіюється тільки перший рівень властивостей.
- Примітиви (
string,number,boolean) копіюються за значенням і стають незалежними. - Вкладені об'єкти, масиви та функції копіюються за посиланням.
- Зміна поля всередині вкладеного об'єкта видно і в оригіналі, і в копії.
- Для незалежної копії використовуйте
structuredClone(),JSON.parse(JSON.stringify())абоcloneDeep()з Lodash.
Швидкий приклад
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) відображається в оригіналі.
Як це виглядає в пам'яті
user.address ────┐
│ (один і той самий об'єкт)
clone.address ────┘Обидва об'єкти, user і clone, посилаються на один і той самий address. Тому зміна всередині address зачіпає обидва. У самих об'єктах user і clone лежить не структура адреси, а адреса цієї структури в пам'яті, і Object.assign() чесно скопіював саме її.
Але примітивні властивості копіюються нормально
const user = { name: 'Alice', age: 25 };
const clone = Object.assign({}, user);
clone.name = 'Oleh';
console.log(user.name); // 'Alice', незалежноПримітив не має внутрішньої структури, яку можна було б розділити, тому в копію лягає саме значення. Спільними лишаються тільки вкладені об'єкти або масиви (а також функції, бо функція теж об'єкт).
Як уникнути цієї проблеми
Якщо потрібна глибока копія, використовуйте один із варіантів.
structuredClone(), вбудований сучасний спосіб:
const clone = structuredClone(user);
clone.address.city = 'Lviv';
console.log(user.address.city); // 'Kyiv', оригінал не зачепленоJSON.parse(JSON.stringify()), універсально, але з обмеженнями (втрачаються undefined, функції, Date перетворюється на рядок, циклічні посилання кидають помилку):
const clone = JSON.parse(JSON.stringify(user));_.cloneDeep() з Lodash, надійно і сумісно з усіма типами:
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.