← Назад к вопросам
Как полностью скопировать объект содержащий ссылки на этот же объект?
2.0 Middle🔥 112 комментариев
#JavaScript Core
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Глубокое копирование объектов с циклическими ссылками
Это одна из сложных задач в JavaScript. Когда объект содержит ссылку на самого себя (прямо или косвенно через цепочку других объектов), стандартные методы копирования могут зависнуть в бесконечном цикле.
Проблема с наивными подходами
Стандартное глубокое копирование не работает с циклическими ссылками:
// Проблема: бесконечный цикл
const obj = { name: 'test' }
obj.self = obj // циклическая ссылка
// JSON.parse(JSON.stringify(obj)) — выбросит ошибку
// Error: Converting circular structure to JSON
function deepCopy(obj) {
if (typeof obj !== 'object') return obj
const copy = Array.isArray(obj) ? [] : {}
for (const key in obj) {
copy[key] = deepCopy(obj[key]) // бесконечный цикл!
}
return copy
}
deepCopy(obj) // Stack overflow!
Решение 1: WeakMap для отслеживания
Самый эффективный способ — использовать WeakMap для хранения уже скопированных объектов:
function deepCopyWithWeakMap(obj, map = new WeakMap()) {
// Базовые типы возвращаем как есть
if (obj === null || typeof obj !== 'object') {
return obj
}
// Если объект уже копирован, вернём его копию
if (map.has(obj)) {
return map.get(obj)
}
// Создаём копию (пустую) и добавляем в map ДО рекурсии
const copy = Array.isArray(obj) ? [] : {}
map.set(obj, copy)
// Теперь копируем свойства
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopyWithWeakMap(obj[key], map)
}
}
return copy
}
// Тестирование
const obj = { name: 'test', data: { value: 42 } }
obj.self = obj
obj.data.parent = obj
const copied = deepCopyWithWeakMap(obj)
console.log(copied.name) // 'test'
console.log(copied.self === copied) // true (циклическая ссылка сохранена)
console.log(copied.data.value) // 42
console.log(copied.data.parent === copied) // true
Решение 2: Map для более подробной отладки
Если нужна совместимость со старыми браузерами или больше контроля:
function deepCopyWithMap(obj, map = new Map(), depth = 0) {
if (depth > 1000) throw new Error('Max depth exceeded')
if (obj === null || typeof obj !== 'object') {
return obj
}
const objId = getObjectId(obj)
if (map.has(objId)) {
return map.get(objId)
}
const copy = Array.isArray(obj) ? [] : {}
map.set(objId, copy)
for (const key of Object.keys(obj)) {
copy[key] = deepCopyWithMap(obj[key], map, depth + 1)
}
return copy
}
let objectCounter = 0
const objectIds = new WeakMap()
function getObjectId(obj) {
if (!objectIds.has(obj)) {
objectIds.set(obj, ++objectCounter)
}
return objectIds.get(obj)
}
Решение 3: Встроенный structuredClone API
В современных браузерах есть встроенный API:
const obj = { name: 'test' }
obj.self = obj
// Работает из коробки!
const copied = structuredClone(obj)
console.log(copied.self === copied) // true
// Поддерживает сложные типы
const complex = {
date: new Date(),
map: new Map([['key', 'value']]),
self: null
}
complex.self = complex
const copiedComplex = structuredClone(complex)
console.log(copiedComplex.date instanceof Date) // true
ВАЖНО: structuredClone не копирует функции и некоторые встроенные объекты.
Решение 4: С поддержкой специальных типов
Если нужна полная поддержка Date, Map, Set и т.д.:
function deepCopyAdvanced(obj, map = new WeakMap()) {
if (obj === null) return null
if (typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj)
// Специальные типы
if (obj instanceof Date) return new Date(obj.getTime())
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
if (obj instanceof Map) {
const newMap = new Map()
map.set(obj, newMap)
obj.forEach((v, k) => {
newMap.set(deepCopyAdvanced(k, map), deepCopyAdvanced(v, map))
})
return newMap
}
if (obj instanceof Set) {
const newSet = new Set()
map.set(obj, newSet)
obj.forEach(v => newSet.add(deepCopyAdvanced(v, map)))
return newSet
}
// Обычные объекты и массивы
const copy = Array.isArray(obj) ? [] : {}
map.set(obj, copy)
for (const key of Object.keys(obj)) {
copy[key] = deepCopyAdvanced(obj[key], map)
}
return copy
}
Рекомендации
- WeakMap подход — лучше всего для production кода, минимум памяти
- structuredClone — используй для современных браузеров, самый простой способ
- С поддержкой типов — когда работаешь с Date, Map, Set и другими встроенными объектами
- Избегай JSON методов — они не работают с циклическими ссылками и теряют типы
Выбор зависит от браузеров, которые нужно поддерживать, и типов данных в приложении.