Клонирование объектов в TypeScript: глубокое и поверхностное копирование
Поверхностное vs глубокое копирование
Вы столкнулись с классической проблемой JavaScript/TypeScript. Когда вы пишете const userCopy = user, вы не создаёте копию — вы создаёте ссылку на тот же объект. Любое изменение через userCopy меняет исходный объект.
Спред-оператор { ...user } делает поверхностное копирование (shallow copy): он копирует только первый уровень свойств. Для примитивов (string, number, boolean) это работает — создаются независимые копии. Но для вложенных объектов и массивов копируются ссылки на те же самые внутренние структуры. Поэтому изменение address.city в копии затрагивает оригинал.
Способы глубокого копирования
Вот основные методы, которые работают в TypeScript:
- JSON.parse/JSON.stringify — самый простой способ, но с ограничениями
- structuredClone — современный нативный метод (доступен в Node 17+, современных браузерах)
- Рекурсивная функция — полный контроль над процессом
- Библиотеки — lodash.cloneDeep для сложных случаев
Пример 1: JSON-метод (простой, но с ограничениями)
function deepCloneJSON<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
const userCopy = deepCloneJSON(user);
userCopy.address.city = 'СПб';
console.log(user.address.city); // 'Москва' — оригинал не изменился
Минусы: теряет функции, undefined, Symbol, специальные объекты (Date, Map, Set превращаются в строки), не работает с циклическими ссылками.
Пример 2: structuredClone (рекомендуемый способ)
const userCopy: User = structuredClone(user);
userCopy.address.city = 'СПб';
userCopy.hobbies.push('плавание');
console.log(user.hobbies); // ['чтение', 'бег'] — без изменений
Преимущества: поддерживает Date, Map, Set, ArrayBuffer, циклические ссылки. Работает быстрее JSON-метода. TypeScript корректно выводит тип.
Пример 3: Рекурсивная функция с поддержкой типов
function deepClone<T>(obj: T, seen = new WeakMap()): T {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
if (obj instanceof Date) return new Date(obj.getTime()) as any;
if (obj instanceof Array) {
const arrCopy = obj.map(item => deepClone(item, seen)) as any;
seen.set(obj, arrCopy);
return arrCopy;
}
const clone = Object.create(Object.getPrototypeOf(obj));
seen.set(obj, clone);
for (const key of Object.keys(obj)) {
clone[key] = deepClone((obj as any)[key], seen);
}
return clone;
}
const userCopy: User = deepClone(user);
Что выбрать?
- Для простых объектов без специальных типов —
structuredClone(или JSON, если нужна поддержка старых сред) - Для максимальной производительности —
structuredClone - Для полного контроля и поддержки редких кейсов — рекурсивная функция
- Для легаси-проектов —
_.cloneDeepиз lodash
Важно: все эти методы создают независимую копию. Изменения в копии никак не влияют на оригинал, даже на глубоких уровнях вложенности.
<<Альтернативный подход: библиотеки и утилиты
Если вы не хотите писать собственные реализации, используйте проверенные библиотеки. Самый популярный вариант — lodash с методом cloneDeep:
import cloneDeep from 'lodash/cloneDeep';
const userCopy: User = cloneDeep(user);
userCopy.address.city = 'СПб'; // оригинал не меняется
Плюсы: проверен годами, поддерживает все типы (Date, Map, Set, RegExp, Buffer), обрабатывает циклические ссылки, работает в Node.js и браузерах.
Минусы: добавляет зависимость (хотя можно импортировать только один метод, а не всю библиотеку).
Когда использовать поверхностное копирование
Иногда глубокое копирование избыточно. Если ваш объект имеет только один уровень вложенности (все поля — примитивы), достаточно спред-оператора:
interface SimpleUser {
name: string;
age: number;
}
const simpleUser: SimpleUser = { name: 'Иван', age: 25 };
const copy = { ...simpleUser };
copy.age = 30; // оригинал не меняется
Для массивов объектов с одним уровнем вложенности можно использовать map со спредом:
const users: User[] = [/* ... */];
const usersCopy = users.map(u => ({ ...u })); // поверхностно
Но помните: если внутри объектов есть вложенные структуры, поверхностное копирование не спасёт — используйте глубокое.
Ответов пока нет.
Войдите чтобы оставить ответ