В чём разница между глубокой и неглубокой копией объекта?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чём разница между глубокой и неглубокой копией объекта
Это фундаментальная концепция в JavaScript, которая часто приводит к багам. Разница связана с тем, как JavaScript работает с объектами в памяти.
Основы: Ссылочные типы
В JavaScript примитивные типы (число, строка, boolean) копируются по значению, а объекты и массивы — по ссылке.
// Примитивы — копируются по значению
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 (не изменился)
// Объекты — копируются по ссылке
let obj1 = { name: 'John' };
let obj2 = obj1; // Копируем ссылку на объект
obj2.name = 'Jane';
console.log(obj1.name); // 'Jane' — изменился!
Неглубокая копия (Shallow Copy)
Неглубокая копия — копируются только значения верхнего уровня. Вложенные объекты остаются ссылками.
// Способ 1: Object.assign
const original = {
name: 'John',
address: { city: 'NYC', zip: '10001' }
};
const shallow = Object.assign({}, original);
// Верхний уровень скопирован
shallow.name = 'Jane';
console.log(original.name); // 'John' — не изменился
// Но вложенный объект остался ссылкой!
shallow.address.city = 'LA';
console.log(original.address.city); // 'LA' — изменился!
// Способ 2: Spread operator (...)
const original = {
name: 'John',
address: { city: 'NYC' }
};
const shallow = { ...original };
// Верхний уровень скопирован
shallow.name = 'Jane';
console.log(original.name); // 'John'
// Вложенный объект остался ссылкой
shallow.address.city = 'LA';
console.log(original.address.city); // 'LA'
// Способ 3: Array.slice() для массивов
const arr1 = [1, 2, { nested: 'value' }];
const arr2 = arr1.slice(); // Неглубокая копия
arr2.push(3);
console.log(arr1); // [1, 2, { nested: 'value' }] (не изменился)
arr2[2].nested = 'changed';
console.log(arr1[2].nested); // 'changed' (изменился!)
Проблема неглубокой копии:
const user = {
name: 'John',
profile: { age: 30, city: 'NYC' }
};
const copy = { ...user };
// Ожидаю, что копия независима
copy.profile.age = 40;
// Ошибка! Изменился оригинал
console.log(user.profile.age); // 40
Глубокая копия (Deep Copy)
Глубокая копия — копируются все уровни, включая вложенные объекты.
// Способ 1: JSON.parse(JSON.stringify()) — быстро, но с ограничениями
const original = {
name: 'John',
address: { city: 'NYC' }
};
const deep = JSON.parse(JSON.stringify(original));
deep.address.city = 'LA';
console.log(original.address.city); // 'NYC' — не изменился
Проблемы JSON.parse(JSON.stringify()):
const obj = {
date: new Date(),
func: () => console.log('hello'),
undef: undefined,
circular: null
};
obj.circular = obj; // Циклическая ссылка
const copy = JSON.parse(JSON.stringify(obj));
// ❌ Ошибка!
// - Date становится строкой
// - Функции удаляются
// - undefined удаляется
// - Циклические ссылки вызывают ошибку
// Способ 2: Рекурсивная копия
function deepCopy(obj) {
// Базовый случай — примитив
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Обработка Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Обработка Array
if (Array.isArray(obj)) {
return obj.map(item => deepCopy(item));
}
// Обработка Object
const copy = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
const original = {
name: 'John',
address: { city: 'NYC', zip: { code: '10001' } },
dates: [new Date(), new Date()]
};
const deep = deepCopy(original);
deep.address.zip.code = '20001';
console.log(original.address.zip.code); // '10001' — не изменился
// Способ 3: Встроенные библиотеки
import _ from 'lodash';
const original = { a: { b: { c: 1 } } };
const deep = _.cloneDeep(original);
deep.a.b.c = 2;
console.log(original.a.b.c); // 1
// Способ 4: structuredClone (современный способ, ES2022)
const original = {
name: 'John',
address: { city: 'NYC' },
date: new Date()
};
const deep = structuredClone(original);
deep.address.city = 'LA';
console.log(original.address.city); // 'NYC'
// ✅ Работает с Date, Map, Set
// ❌ Не работает с функциями и циклическими ссылками
Сравнение методов
| Метод | Скорость | Date | Function | Циклические ссылки | Сложность |
|---|---|---|---|---|---|
| spread (...) | Очень быстро | ✅ | ✅ | ✅ | Низкая |
| JSON.parse | Быстро | ❌ | ❌ | ❌ | Низкая |
| structuredClone | Среднее | ✅ | ❌ | ❌ | Низкая |
| Рекурсия | Медленно | ✅ | ✅ | ❌ | Высокая |
| Lodash cloneDeep | Быстро | ✅ | ✅ | ✅ | Низкая |
Практические примеры в React
Проблема: неглубокая копия в useState
function UserForm() {
const [user, setUser] = useState({
name: 'John',
address: { city: 'NYC' }
});
const handleCityChange = (city: string) => {
// ❌ Неправильно — изменяет оригинальный объект
user.address.city = city;
setUser(user);
// ❌ React может не заметить изменения!
};
return <div>{user.address.city}</div>;
}
Решение: глубокая копия
function UserForm() {
const [user, setUser] = useState({
name: 'John',
address: { city: 'NYC' }
});
const handleCityChange = (city: string) => {
// ✅ Правильно — создаём новый объект
setUser(prev => ({
...prev,
address: {
...prev.address,
city
}
}));
};
return <div>{user.address.city}</div>;
}
Или используй structuredClone:
const handleCityChange = (city: string) => {
const newUser = structuredClone(user);
newUser.address.city = city;
setUser(newUser);
};
Когда использовать что
Неглубокая копия достаточно когда:
- Только primitives в объекте
- Вложенные объекты не меняются
- Нужна максимальная скорость
Нужна глубокая копия когда:
- Вложенные объекты/массивы
- Меняешь вложенные данные
- Redux/состояние приложения
- Работаешь с форм данными
Вывод: в большинстве случаев используй spread/Object.assign для простых объектов и structuredClone или библиотеки для сложных случаев.