function deepClone(value, seen = new WeakMap()) { // Примитивы и функции возвращаем как есть if (value === null || typeof value !== 'object') { return value; } // Циклическая ссылка: уже клонировали этот объект — вернём копию if (seen.has(value)) { return seen.get(value); } // Спец-типы if (value instanceof Date) return new Date(value); if (value instanceof RegExp) return new RegExp(value.source, value.flags); if (value instanceof Map) { const result = new Map(); seen.set(value, result); value.forEach((v, k) => result.set(k, deepClone(v, seen))); return result; } if (value instanceof Set) { const result = new Set(); seen.set(value, result); value.forEach((v) => result.add(deepClone(v, seen))); return result; } // Массивы и обычные объекты const result = Array.isArray(value) ? [] : {}; seen.set(value, result); // регистрируем ДО рекурсии — иначе цикл зациклит for (const key of Reflect.ownKeys(value)) { result[key] = deepClone(value[key], seen); } return result;}
Ключевые моменты
WeakMapseen хранит соответствие оригинал → копия. Регистрировать копию нужно до обхода полей, иначе циклическая ссылка приведёт к бесконечной рекурсии.
Reflect.ownKeys захватывает и строковые ключи, и символы.
Спец-типы (Date, Map, Set, RegExp) нельзя копировать через spread — нужен явный конструктор.
Быстрый способ (structuredClone)
// Встроенный API (Node 17+, современные браузеры).// Поддерживает циклы, Date, Map, Set, но НЕ функции и не прототипы классов.const copy = structuredClone(obj);