Чем отличается shallow copy от deep copy в JavaScript?
Поверхностное и глубокое копирование
Shallow copy (поверхностная копия) создаёт новый объект верхнего уровня, но свойства, которые содержат ссылки на другие объекты или массивы, копируются как ссылки. В результате оригинал и копия делят одни и те же вложенные объекты: изменение вложенного свойства в копии затронет и оригинал.
Deep copy (глубокая копия) рекурсивно обходит все уровни вложенности и создаёт независимые копии вложенных объектов и массивов. Изменения во вложенных данных копии никак не влияют на оригинал.
Способы создания shallow copy
- Spread-оператор:
{ ...obj },[ ...arr ] Object.assign({}, obj)Array.prototype.slice(),Array.from()
Все перечисленные способы копируют только первый уровень свойств.
Способы создания deep copy
structuredClone(obj)— современный встроенный API. Поддерживает Map, Set, Date, ArrayBuffer, циклические ссылки, но не клонирует функции и DOM-узлы.JSON.parse(JSON.stringify(obj))— простой, но теряет функции,undefined,Symbol, превращаетDateв строку и падает на циклических ссылках.- Библиотека
lodash.cloneDeep— надёжный вариант для сложных структур с сохранением типов. - Ручная рекурсивная функция — полный контроль над логикой копирования.
Типичная проблема
При поверхностном копировании объекта с вложенным объектом изменение вложенного свойства копии неожиданно меняет оригинал — это частый источник багов в реактивных приложениях (React, Redux), где ожидается иммутабельность.
Когда что использовать
- Shallow copy достаточно для плоских объектов или когда нужно обновить только свойства верхнего уровня — например, при иммутабельных обновлениях state в Redux (
{ ...state, loading: true }). - Deep copy нужен, если объект содержит вложенные структуры и требуется, чтобы копия была полностью независима от оригинала — например, при работе с формами, снапшотами состояния или undo/redo.
Производительность
Deep copy дороже по времени и памяти: рекурсивный обход больших вложенных структур создаёт заметную нагрузку. Не стоит применять глубокое копирование там, где достаточно поверхностного — особенно в горячих участках кода (рендер, обработчики частых событий).
Циклические ссылки
JSON.stringify бросает исключение на циклических ссылках, а structuredClone и lodash.cloneDeep умеют их корректно обрабатывать, сохраняя структуру графа объектов.
Что хочет услышать интервьюер
Чёткое определение: shallow copy копирует ссылки на вложенные объекты, deep copy — сами данные рекурсивно
Знание нескольких способов поверхностного копирования (spread, Object.assign, slice)
Знание способов глубокого копирования, включая structuredClone и его ограничения
Понимание, почему JSON.parse(JSON.stringify()) не универсален (теряет функции, Date, Symbol, циклические ссылки)
Осознание компромисса между производительностью и необходимостью полной независимости копии
Пример: Проблема shallow copy с вложенным объектом
const original = {
name: 'Anton',
address: { city: 'Moscow' },
};
// Поверхностная копия — верхний уровень новый, address всё та же ссылка
const shallow = { ...original };
shallow.name = 'Ivan';
shallow.address.city = 'Saint Petersburg';
console.log(original.name); // 'Anton' — не изменилось
console.log(original.address.city); // 'Saint Petersburg' — изменилось! общая ссылка
Пример: Deep copy через structuredClone
const original = {
name: 'Anton',
address: { city: 'Moscow' },
createdAt: new Date(),
};
// Глубокая копия: вложенные объекты полностью независимы
const deep = structuredClone(original);
deep.address.city = 'Saint Petersburg';
console.log(original.address.city); // 'Moscow' — не затронуто
console.log(deep.createdAt instanceof Date); // true, тип сохранён
Пример: Ограничения JSON.parse(JSON.stringify())
const original = {
fn: () => 'hello',
date: new Date(),
value: undefined,
id: Symbol('id'),
};
const copy = JSON.parse(JSON.stringify(original));
console.log(copy.fn); // undefined — функция потеряна
console.log(typeof copy.date); // 'string' — Date превратился в строку
console.log('value' in copy); // false — undefined-свойства выбрасываются
console.log(copy.id); // undefined — Symbol потерян
Типичные ошибки
Считают, что spread-оператор или Object.assign создают полностью независимую копию объекта
Не знают об ограничениях JSON.parse(JSON.stringify()) — потере функций, undefined, Symbol, преобразовании Date в строку
Не упоминают structuredClone как современную встроенную альтернативу для deep copy
Путают копирование массива примитивов (где spread действительно даёт независимую копию) с массивом объектов
Не могут объяснить, почему мутация вложенного объекта после shallow copy ломает иммутабельность в React/Redux


