Как правильно клонировать объект в JavaScript: поверхностное и глубокое копирование
Проблема ссылочного копирования в JavaScript
Вы совершенно правы: в JavaScript объекты (и массивы) хранятся и копируются по ссылке, а не по значению. Когда вы пишете let clone = user, вы не создаёте новый объект — вы создаёте ещё одну переменную, которая указывает на тот же самый объект в памяти. Поэтому изменение через одну переменную сразу видно через другую.
Для создания независимой копии нужно использовать клонирование. Оно бывает двух типов:
- Поверхностное (shallow copy) — копирует только свойства первого уровня. Вложенные объекты остаются общими.
- Глубокое (deep copy) — создаёт полностью независимую копию со всеми уровнями вложенности.
1. Поверхностное копирование
Эти методы подходят, если объект не содержит вложенных объектов (только примитивы: строки, числа, булевы значения, null, undefined).
Способ A: Object.assign()
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original);
copy.a = 99;
console.log(original.a); // 1 — оригинал не изменился
Способ B: Spread-оператор (...)
const original = { a: 1, b: 2 };
const copy = { ...original };
copy.a = 99;
console.log(original.a); // 1
Оба способа работают идентично для плоских объектов. Но для вложенных они не спасают.
2. Глубокое копирование
Способ A: JSON.parse + JSON.stringify (простой, но с ограничениями)
Это самый популярный «быстрый» метод для глубокого копирования:
const original = {
name: "Анна",
address: { city: "Москва" },
skills: ["JS", "Python"]
};
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.address.city = "СПб";
deepCopy.skills.push("Go");
console.log(original.address.city); // "Москва" — не изменилось
console.log(original.skills); // ["JS", "Python"] — не изменилось
Важные ограничения этого метода:
- Не копирует функции,
undefined,Symbol— они будут потеряны или заменены наnull. - Не работает с циклическими ссылками (объект ссылается сам на себя) — вызовет ошибку.
- Не сохраняет специальные объекты:
Dateстанет строкой,Map,Set,RegExp— потеряют свой тип.
Способ B: structuredClone() (современный и правильный)
Начиная с ES2022 (современные браузеры и Node.js 17+), есть встроенная функция structuredClone(). Она решает почти все проблемы:
const original = {
name: "Анна",
date: new Date(),
nested: { arr: [1, 2, { x: 10 }] },
undef: undefined,
func: () => console.log("Привет")
};
const clone = structuredClone(original);
clone.nested.arr[2].x = 999;
console.log(original.nested.arr[2].x); // 10 — не изменилось
console.log(clone.date instanceof Date); // true — дата осталась датой
Что умеет structuredClone:
- Глубокое копирование любой глубины.
- Работает с
Date,Map,Set,RegExp,ArrayBufferи другими. - Корректно обрабатывает циклические ссылки.
- Не копирует функции и методы (выбрасывает ошибку, если они есть).
3. Самодельная функция для глубокого копирования (рекурсия)
Если вам нужно копировать и функции, или вы работаете в старой среде, можно написать свою функцию:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj; // примитивы
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
const clonedObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
// Пример:
const original = { a: 1, b: { c: 2 }, greet: () => "Привет" };
const copy = deepClone(original);
console.log(copy.greet()); // "Привет" — функция скопировалась
Итоговая рекомендация:
- Для простых объектов без вложений — используйте
{ ...obj }илиObject.assign({}, obj). - Для глубокого копирования в современном коде —
structuredClone(obj). - Если нужна поддержка функций или старых браузеров — напишите рекурсивную функцию или используйте библиотеку (например, Lodash с методом
_.cloneDeep()).
Альтернативный подход: библиотека Lodash
Если вы не хотите писать свою реализацию или использовать JSON.parse/JSON.stringify с его ограничениями, отличным решением будет популярная библиотека Lodash. Она предоставляет готовый метод _.cloneDeep(), который делает глубокое копирование правильно и без «сюрпризов».
Установите Lodash через npm (или подключите CDN) и используйте:
// npm install lodash
const _ = require('lodash');
const original = {
name: "Ольга",
address: { city: "Казань" },
tags: new Set(["js", "node"]),
actions: [() => console.log("action")]
};
const clone = _.cloneDeep(original);
clone.address.city = "Уфа";
console.log(original.address.city); // "Казань"
// Lodash корректно копирует Set, Map, Date, функции и даже циклические ссылки
Преимущества Lodash:
- Проверенное годами решение — используется в миллионах проектов.
- Работает с функциями, циклическими ссылками, специальными объектами.
- Не требует писать рекурсивный код вручную.
- Есть и поверхностный вариант:
_.clone().
Недостаток: нужно подключать библиотеку (даже если вам нужен только один метод). Но современные сборщики (Webpack, Vite) позволяют импортировать только нужную функцию, чтобы не раздувать размер бандла:
import cloneDeep from 'lodash/cloneDeep'; // импорт только одной функции
Таким образом, если вы работаете в проекте, где уже используется Lodash, или вам нужно надёжное решение «из коробки» — это отличный выбор.