Как правильно клонировать объект в JavaScript? Проблемы с Object.assign и spread оператором
Проблема поверхностного копирования
Вы правы — Object.assign() и spread оператор (...) создают только поверхностную копию. Это значит, что вложенные объекты и массивы копируются по ссылке, а не по значению. Для независимой копии нужно использовать глубокое (deep) клонирование.
1. Лучший современный способ: structuredClone()
Начиная с ES2021 (и доступно в Node.js 17+, Chrome 98+, Firefox 94+), существует встроенная функция structuredClone(). Это самый правильный и безопасный способ:
const user = {
name: 'Иван',
age: 30,
address: { city: 'Москва', street: 'Тверская' },
hobbies: ['программирование', 'шахматы'],
date: new Date() // structuredClone корректно копирует даты
};
const deepClone = structuredClone(user);
// Теперь изменения не влияют на оригинал
deepClone.address.city = 'Санкт-Петербург';
deepClone.hobbies.push('чтение');
console.log(user.address.city); // 'Москва' — оригинал не изменился
console.log(user.hobbies.length); // 2 — оригинал не изменился
console.log(deepClone.address.city); // 'Санкт-Петербург'
2. Классический способ: JSON.parse/JSON.stringify
Этот метод работает везде, но имеет ограничения:
const jsonClone = JSON.parse(JSON.stringify(user));
jsonClone.address.city = 'Казань';
console.log(user.address.city); // 'Москва' — работает
Ограничения метода:
- Не копирует функции,
undefined,Symbol - Некорректно обрабатывает
Date(превращает в строку),Set,Map,RegExp - Не работает с циклическими ссылками
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 (hash.has(obj)) return hash.get(obj); // защита от циклических ссылок
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (const key of Object.keys(obj)) {
clone[key] = deepClone(obj[key], hash);
}
return clone;
}
// Пример с циклической ссылкой
const cyclicObj = { name: 'Цикл' };
cyclicObj.self = cyclicObj;
const safeClone = deepClone(cyclicObj);
console.log(safeClone.self === safeClone); // true — циклическая ссылка сохранена
4. Сторонние библиотеки (если нужно в старых браузерах)
- Lodash:
_.cloneDeep(obj)— самый популярный вариант - Ramda:
R.clone(obj)
Что выбрать?
- Всегда используйте
structuredClone()— если ваша среда выполнения его поддерживает (проверьте черезtypeof structuredClone === 'function') - JSON.parse/JSON.stringify — для простых объектов без специальных типов
- Рекурсивная функция — когда нужен полный контроль или поддержка циклических ссылок
- Lodash — если вы уже используете эту библиотеку в проекте
Избегайте поверхностного копирования с Object.assign или spread, если объект содержит вложенные структуры.
Альтернативный подход: библиотека Lodash
Если вы не можете использовать structuredClone (например, работаете со старыми браузерами или Node.js версии ниже 17), отличным решением будет библиотека Lodash и её метод _.cloneDeep().
// Установка: npm install lodash
import _ from 'lodash';
const user = {
name: 'Иван',
address: { city: 'Москва' },
hobbies: ['программирование'],
birthDate: new Date('1993-01-01'),
skills: new Set(['JS', 'React']),
meta: undefined
};
const cloned = _.cloneDeep(user);
cloned.address.city = 'Санкт-Петербург';
cloned.hobbies.push('чтение');
console.log(user.address.city); // 'Москва' — оригинал сохранен
console.log(cloned.birthDate instanceof Date); // true — дата скопирована корректно
Преимущества Lodash:
- Работает во всех средах (включая IE11)
- Поддерживает
Date,RegExp,Set,Map,ArrayBufferи другие типы - Корректно обрабатывает циклические ссылки
- Хорошо оптимизирован для больших объектов
Недостаток: добавляет зависимость (~4 КБ в сжатом виде, если использовать только этот метод через lodash.clonedeep).
Если проект уже использует Lodash — это идеальный выбор. Для новых проектов рекомендую structuredClone, так как он встроенный и не требует дополнительных зависимостей.
Ответов пока нет.
Войдите чтобы оставить ответ