Поверхностное и глубокое копирование объектов

Частый практический вопрос: «Как скопировать объект, чтобы не задеть оригинал?»

Поверхностная копия дублирует только верхний уровень; вложенные объекты остаются общими ссылками. Глубокая копия рекурсивно копирует всё.

Поверхностная копия делит вложенное

spread и Object.assign копируют только верхний уровень. Вложенный объект — это та же ссылка, поэтому его мутация видна в обеих копиях.

const original = { name: "Аня", address: { city: "Москва" } };
const shallow = { ...original };

shallow.name = "Боря";              // верхний уровень — независим
shallow.address.city = "Казань";    // вложенное — общее!

console.log(original.name);         // не изменилось
console.log(original.address.city); // изменилось!

Вывод:

Аня
Казань

Это классический баг: думали, что скопировали объект целиком, а на деле два «клона» делят вложенную структуру.

Глубокая копия через JSON

Простой способ глубокой копии — сериализовать в JSON и обратно. Тогда копируется вся структура целиком.

const original = { name: "Аня", address: { city: "Москва" } };
const deep = JSON.parse(JSON.stringify(original));

deep.address.city = "Казань";

console.log(original.address.city); // оригинал цел
console.log(deep.address.city);

Вывод:

Москва
Казань

Ограничения JSON-копии

Способ через JSON не идеален: он теряет функции, undefined, Symbol, превращает Date в строку и падает на циклических ссылках. Это любят спросить «а в чём подвох».

const obj = {
  fn: () => 1,
  value: undefined,
  when: new Date(0),
  num: 5,
};

const copy = JSON.parse(JSON.stringify(obj));
console.log("fn" in copy);    // функция потеряна
console.log("value" in copy); // undefined потеряно
console.log(typeof copy.when); // Date стала строкой
console.log(copy.num);

Вывод:

false
false
string
5

Современный способ: structuredClone

В современных средах есть встроенный structuredClone(obj) — он делает глубокую копию, поддерживает Date, Map, Set и циклические ссылки (но не копирует функции). На собеседовании достаточно упомянуть его как правильный современный вариант.

Памятка

СпособГлубинаПодвох
{...obj}поверхностнаявложенное общее
JSON.parse(JSON.stringify)глубокаятеряет функции, Date→строка
structuredCloneглубокаяне копирует функции

Итог

  • Поверхностная копия делит вложенные объекты — мутация видна в оригинале.
  • JSON.parse(JSON.stringify) делает глубокую копию, но теряет функции и Date.
  • Современный правильный вариант — structuredClone.
Проверьте себя
1. Что копирует spread {...obj}?
AВесь объект рекурсивно
BТолько верхний уровень; вложенные объекты остаются общими ссылками
CНичего не копирует
DТолько числа
2. Какой подвох у глубокой копии через JSON.parse(JSON.stringify(obj))?
AОна работает медленнее любой другой
BТеряются функции, undefined, Symbol; Date превращается в строку
CОна не копирует числа
DОна мутирует оригинал
3. Какой современный встроенный способ глубокого копирования?
AObject.assign
BstructuredClone
CArray.from
Dclone()
Поддержать проект