Самый эффективный способ глубокого клонирования объекта в JavaScript
Проблема поверхностного копирования
Вы правы: Object.assign() и spread-оператор создают только поверхностную копию. Это значит, что вложенные объекты и массивы остаются общими ссылками. Для глубокого копирования есть несколько подходов, каждый со своими плюсами и минусами.
1. JSON.parse/JSON.stringify — простой, но ограниченный
const deepClone = obj => JSON.parse(JSON.stringify(obj));
const original = {
name: 'Анна',
date: new Date(),
func: () => console.log('test'),
undef: undefined,
regexp: /test/gi
};
const clone = deepClone(original);
console.log(clone.date); // строка, не Date!
console.log(clone.func); // undefined — функция потеряна
console.log(clone.undef); // undefined — но в JSON нет undefined
console.log(clone.regexp); // {} — RegExp превращается в пустой объект
Минусы: Не работает с функциями, undefined, Symbol, Date, RegExp, Map, Set, циклическими ссылками. Подходит только для простых JSON-совместимых данных.
2. structuredClone() — современный нативный метод (рекомендуется)
Начиная с 2022 года, в браузерах и Node.js 17+ доступна функция structuredClone():
const original = {
name: 'Анна',
date: new Date(),
nested: { arr: [1, 2, { a: 1 }] },
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3])
};
const clone = structuredClone(original);
clone.nested.arr[2].a = 999;
console.log(original.nested.arr[2].a); // 1 — не изменилось!
console.log(clone.date instanceof Date); // true — Date сохранился
Преимущества:
- Поддерживает
Date,RegExp,Map,Set,ArrayBuffer,Blob,ImageDataи другие - Работает с циклическими ссылками
- Самый быстрый нативный метод
- Безопасный — не выполняет произвольный код
Ограничения: Не копирует функции, Symbol, WeakMap, WeakSet, DOM-узлы.
3. Рекурсивная функция — полный контроль
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) return new Map([...obj].map(([k, v]) => [deepClone(k, hash), deepClone(v, hash)]));
if (obj instanceof Set) return new Set([...obj].map(v => deepClone(v, hash)));
// Обработка циклических ссылок
if (hash.has(obj)) return hash.get(obj);
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
// Копирование Symbol-ключей
const symbols = Object.getOwnPropertySymbols(obj);
for (const sym of symbols) {
clone[sym] = deepClone(obj[sym], hash);
}
return clone;
}
// Пример с циклической ссылкой
const circular = { name: 'test' };
circular.self = circular;
const cloned = deepClone(circular);
console.log(cloned.self === cloned); // true — циклическая ссылка сохранена
Что выбрать в 2024 году?
- Для 90% случаев: используйте
structuredClone(). Это самый быстрый и надёжный способ для современных проектов - Для Node.js < 17 или старых браузеров: используйте библиотеку
lodash.cloneDeep()— проверенное решение с хорошей поддержкой - Если нужны функции и Symbol: пишите свою рекурсивную реализацию или используйте
rfdc(really fast deep clone) — очень быструю библиотеку
Производительность: structuredClone() в 2-10 раз быстрее JSON.parse/stringify для больших объектов и в 50+ раз быстрее рекурсивной реализации. Всегда тестируйте на своих данных.
Альтернативный подход: библиотеки и утилиты
Если вы не можете использовать structuredClone() из-за поддержки старых браузеров или вам нужны дополнительные возможности, обратите внимание на проверенные библиотеки:
Lodash (самая популярная)
// Установка: npm i lodash
import cloneDeep from 'lodash/cloneDeep';
const original = { a: 1, b: { c: 2 }, d: [1, 2, 3] };
const clone = cloneDeep(original);
clone.b.c = 999;
console.log(original.b.c); // 2 — не изменилось
Плюсы: Работает с функциями, Symbol, циклическими ссылками. Проверена миллионами проектов.
Минусы: Размер библиотеки (~24 KB gzip), но можно импортировать только cloneDeep.
rfdc (Really Fast Deep Clone)
// Установка: npm i rfdc
const clone = require('rfdc')();
const fast = clone({ a: 1, b: { c: 2 } });
Плюсы: Один из самых быстрых клонов (написан на C++ через Node.js addon). Работает с Date, RegExp, Map, Set.
Минусы: Не копирует функции и Symbol.
Совет для начинающих
Начните с structuredClone() — это встроенная функция, не требующая установки библиотек. Если проект требует поддержки IE11 или старых версий Node.js, используйте lodash.cloneDeep. И помните: глубокое клонирование больших объектов может быть дорогим по памяти, поэтому старайтесь минимизировать его использование в критичных участках кода.