Практика: как клонировать объект в JS?

🎯 Зачем спрашивают
  • Проверить, понимает ли кандидат разницу между поверхностным (shallow) и глубоким (deep) копированием.
  • Проверить знание стандартных инструментов JS (spread, Object.assign, JSON.parse(JSON.stringify), structuredClone) и их ограничений.
  • Понять, умеет ли кандидат видеть edge-cases (даты, функции, символы, циклические ссылки).
  • Увидеть, как он рассуждает о trade-offs: простота vs полнота, библиотека (lodash) vs самописное решение.
  • Понять, знает ли кандидат, что часто deep clone — имеет ряд своих минусов, и лучше проектировать данные иммутабельно.
 
📝 Ответ
Коротко:
Самый надежный способ — использовать функцию глубокого копирования. То есть функция бегает по ключам и значения, рекурсивно, и копирует их в новый объект.
function deepClone(obj) { // Примитивы и функции возвращаем как есть if (obj === null || typeof obj !== "object") { return obj; } // Дата if (obj instanceof Date) { return new Date(obj); } // Массив if (Array.isArray(obj)) { return obj.map(item => deepClone(item)); } // Map if (obj instanceof Map) { return new Map([...obj.entries()].map(([k, v]) => [deepClone(k), deepClone(v)])); } // Set if (obj instanceof Set) { return new Set([...obj].map(v => deepClone(v))); } // Обычный объект const clonedObj = {}; for (let key in obj) { if (Object.hasOwn(obj, key)) { clonedObj[key] = deepClone(obj[key]); } } return clonedObj; }
 
Коротко о методах:
Метод
Поддержка
Проблемы
Где применять
Spread / assign
Shallow
Вложенные объекты остаются по ссылке
Простые объекты
JSON.stringify/parse
Deep (частично)
Теряются функции, undefined, Symbol, NaN, Infinity
Простые данные
structuredClone
Deep (современный)
Не клонирует функции, Symbol, WeakMap/WeakSet
Современный стандарт
_.cloneDeep
Deep
Тяжёлая зависимость
Legacy проекты
Самописный deepClone
Deep (под твой use-case)
Нужно самому поддерживать edge-cases
Когда нужна гибкость
 
Подробнее:
Есть несколько способов:
  • поверхностное копирование (shallow copy)
  • глубокое копирование (deep copy)
 
Поверхностное копирование (shallow clone) Копируются только верхние уровни объекта. Вложенные объекты и массивы остаются по ссылке.
 
#1. Spread operator
const obj = { a: 1, b: { c: 2 } }; const clone = { ...obj }; clone.a = 42; // не затронет obj clone.b.c = 99; // изменит obj.b.c !!!
#2. Object.assign
const obj = { a: 1, b: { c: 2 } }; const clone = Object.assign({}, obj);
То же самое — копия только первого уровня.
 

 
Глубокое копирование (deep clone)
Копируются все уровни вложенности.
#1 JSON.stringify/JSON.parse
const original = { num: 1, str: "hello", bool: true, undef: undefined, sym: Symbol("id"), func: () => "I am a function", date: new Date(), nested: { x: 10 }, }; const clone = JSON.parse(JSON.stringify(original)); console.log(clone.num); // 1 ✅ console.log(clone.str); // "hello" ✅ console.log(clone.bool); // true ✅ console.log(clone.undef); // undefined → ❌ потерялось console.log(clone.sym); // Symbol → ❌ потерялся console.log(clone.func); // Function → ❌ потерялась console.log(clone.date); // строка, а не Date ❌ console.log(clone.nested); // { x: 10 } ✅
✅ Плюсы
❌ Минусы
Не работает с функциями
Теряет undefined, Symbol
Не подходит для Date, Map, Set и циклических ссылок
Потеря NaN, Infinity, Infinity → они превращаются в null
Не клонирует WeakMap/WeakSet и DOM-ноды
💡
Победить минусы данного решения по работе с нетривиальными (Date, Map, Set) конструкциями можно
Но придется придумать свой контракт даных и парсер. Представим, что вам нужно заставить текущее решение работать с датами. Тогда вы можете добавить поле, явно сигнализирующее парсерсу о типе данных.
{ date: { value: new Date(), type: 'date' } }
// ... if(entity.type === 'date') return new Date(entity.value)
#2. structuredClone (современный стандарт)
const original = { num: 1, str: "hello", bool: true, undef: undefined, sym: Symbol("id"), func: () => "I am a function", date: new Date(), nested: { x: 10 }, }; const clone = structuredClone(original); console.log(clone.num); // 1 ✅ console.log(clone.str); // "hello" ✅ console.log(clone.bool); // true ✅ console.log(clone.date); // Date ✅ (правильный объект даты) console.log(clone.nested); // { x: 10 } ✅ console.log(clone.undef); // undefined ✅ сохраняется console.log(clone.sym); // ❌ Ошибка (Symbol не клонируется) console.log(clone.func); // ❌ Ошибка (Function не клонируется)
✅ Плюсы
❌ Минусы
Работает с Date, Map, Set, ArrayBuffer
Не клонирует функции
#3. Мануальное копирование
  • Lodash: _.cloneDeep(obj)
  • Самописное решение
function deepClone(obj) { // Примитивы и функции возвращаем как есть if (obj === null || typeof obj !== "object") { return obj; } // Дата if (obj instanceof Date) { return new Date(obj); } // Массив if (Array.isArray(obj)) { return obj.map(item => deepClone(item)); } // Map if (obj instanceof Map) { return new Map([...obj.entries()].map(([k, v]) => [deepClone(k), deepClone(v)])); } // Set if (obj instanceof Set) { return new Set([...obj].map(v => deepClone(v))); } // Обычный объект const clonedObj = {}; for (let key in obj) { if (Object.hasOwn(obj, key)) { clonedObj[key] = deepClone(obj[key]); } } return clonedObj; }
 
⚖️ Компромиссы
Метод
Поддержка
Проблемы
Где применять
Spread / assign
Shallow
Вложенные объекты остаются по ссылке
Простые объекты
JSON.stringify/parse
Deep (частично)
Теряются функции, undefined, Symbol, NaN, Infinity
Простые данные
structuredClone
Deep (современный)
Не клонирует функции, Symbol, WeakMap/WeakSet
Современный стандарт
_.cloneDeep
Deep
Тяжёлая зависимость
Legacy проекты
Самописный deepClone
Deep (под твой use-case)
Нужно самому поддерживать edge-cases
Когда нужна гибкость
 
🔎 Встречные вопросы
  • Как клонировать объект с циклической ссылкой?
  • structuredClone есть не везде (Node < 17). Чем заменишь?
  • Когда лучше вообще не делать deep clone, а проектировать по-другому (immutable updates, нормализация state)?
 
🚩 Красные флаги
  • Просто использую JSON.parse(JSON.stringify(obj), работает всегда.
  • Spread делает deep clone.
  • Невозможность объяснить, почему structuredClone лучше JSON.
 
🛠 Практика
  • Реализуй deepClone для объекта с Date и Map.
  • Попробуй клонировать объект с циклической ссылкой — что произойдёт?
  • Сравни производительность JSON vs structuredClone на 100k объектов.
 
📚 Источники / ссылки