Глубокое клонирование — один из тех сценариев, где стандартные решения либо теряют типы, либо падают с ошибками. В production это критично: при клонировании состояния в стейт-менеджменте, кэшировании данных из API или передаче объектов между воркерами. Частая ошибка — полагаться на
JSON.parse(JSON.stringify(obj)) во всех случаях, не учитывая, что он не поддерживает Map, Set, Date, BigInt и циклические ссылки.✅ structuredClone — стандарт для большинства сценариев
Современный API, доступный в браузерах и Node.js 17+. Поддерживает
Map, Set, ArrayBuffer, Blob, Date, RegExp, Error и циклические ссылки. Работает быстро, но не клонирует функции, WeakMap и WeakSet, а также теряет прототипы с геттерами и сеттерами. В production — базовый выбор.const obj = {
a: 1,
map: new Map([['key', 'val']]),
date: new Date()
};
obj.self = obj;
const clone = structuredClone(obj);
// clone.map.get('key') -> 'val'
// clone.self === clone -> true⚠️ JSON.parse/JSON.stringify — опасный компромисс
Подходит только для плоских объектов с примитивами. Потеря
undefined, функций, BigInt, всех типов коллекций и падение на циклических ссылках делает его ненадёжным даже для простых API-ответов. Используйте только для чистых JSON-данных, где типобезопасность не требуется.🔧 Кастомная рекурсия для полного контроля
Когда нужно сохранить прототипы, геттеры или работать с
WeakMap/WeakSet, пишите рекурсию с WeakMap для защиты от циклов. Пример кукбука для экзотических типов:function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
let clone;
if (obj instanceof WeakMap) {
clone = new WeakMap();
hash.set(obj, clone);
obj.forEach((val, key) => clone.set(key, deepClone(val, hash)));
return clone;
}
if (obj instanceof Map) {
clone = new Map();
hash.set(obj, clone);
obj.forEach((val, key) => clone.set(key, deepClone(val, hash)));
return clone;
}
// Аналогично для Set, WeakSet
clone = Object.create(Object.getPrototypeOf(obj));
hash.set(obj, clone);
const descriptors = Object.getOwnPropertyDescriptors(obj);
for (const key of Object.keys(descriptors)) {
const desc = descriptors[key];
if (desc.get || desc.set) {
Object.defineProperty(clone, key, desc);
} else {
clone[key] = deepClone(desc.value, hash);
}
}
return clone;
}Практический совет: для
WeakMap аккуратно обрабатывайте ключи — они объекты, и их тоже нужно клонировать, иначе уникальность ссылок потеряется. Типичная ошибка — забыть обработать WeakMap в решении, что приводит к утечкам при кэшировании.Вывод:
structuredClone покрывает 90% сценариев, а кастомную рекурсию оставляйте для случаев, где важны прототипы, функции и WeakMap/WeakSet — тогда баланс между производительностью и типобезопасностью оправдан.