🎯 Зачем спрашивают
- Проверить, понимает ли кандидат разницу между поверхностным (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 объектов.