Коротка відповідь
juniorГлибока копія створює новий об’єкт і рекурсивно дублює всі вкладені структури, а поверхнева копія лише копіює властивості верхнього рівня. Для shallow можна використати `Object.assign({}, obj)` або розпакування `{...obj}`; для deep – рекурсивну функцію, `JSON.parse(JSON.stringify(obj))` або бібліотеку lodash (`_.cloneDeep`).
Повне пояснення
Що це і навіщо
- Поверхнева копія (shallow copy) – новий об’єкт, у якому властивості рівня 0 копіюються за значенням. Якщо властивість є об’єктом, у новому об’єкті зберігається той самий посилання.
- Глибока копія (deep copy) – рекурсивне дублювання всіх вкладених об’єктів і масивів, так що новий об’єкт повністю незалежний від оригіналу.
Ключові принципи
- Поверхнева копія достатня, коли дані не містять вкладених об’єктів або їх зміни не потрібні.
- Глибока копія необхідна, коли потрібно ізолювати стан або працювати з глибинними структурами.
Як це працює
- Shallow: копіювання властивостей за допомогою
Object.assignабо розпакування{...obj}. Операція O(n) за кількістю властивостей. - Deep: рекурсія по всіх значеннях, перевірка типу (
Array.isArray,typeof === 'object' && obj !== null).
Практика і реалізація
// Shallow copy
const shallow = Object.assign({}, obj); // або const shallow = {...obj};
// Deep copy – простий варіант (не обробляє функції, Symbol тощо)
const deep = JSON.parse(JSON.stringify(obj));
// Deep copy – рекурсивна функція
function cloneDeep(value) {
if (Array.isArray(value)) return value.map(cloneDeep);
if (value && typeof value === 'object') {
const res = {};
for (const k in value) res[k] = cloneDeep(value[k]);
return res;
}
return value; // примітив
}
const deep2 = cloneDeep(obj);
Тестування
import { describe, it } from 'vitest';
import { expect } from 'vitest';
describe('cloneDeep', () => {
it('should clone nested objects', () => {
const a = { x: { y: 1 } };
const b = cloneDeep(a);
expect(b).not.toBe(a);
expect(b.x).not.toBe(a.x);
expect(b.x.y).toBe(1);
});
});
Дизайн‑рішення
- Для простих даних використовуйте
JSON.parse(JSON.stringify(...))– швидко, але обмежено. - Для складних структур або коли потрібна підтримка функцій/Date/Map/Set використовуйте
lodash.cloneDeepабо власну рекурсивну реалізацію.
Оптимізація та edge‑cases
JSON.stringifyне копіює функції, Date, RegExp, Map/Set.- Рекурсивна копія може викликати переповнення стеку при глибоких структурах; використовуйте ітеративний підхід або збільшення стеку.
- У випадку циклічних посилань (
obj.a = obj) рекурсивна функція зазнає помилки; треба вести карту вже скопійованих об’єктів.
Безпека
- При копіюванні даних з зовнішніх джерел перевіряйте типи, щоб уникнути XSS‑уразливостей.
Чеклист діагностики
- Чи потрібна глибока копія? – Якщо об’єкт містить вкладені структури.
- Чи є циклічні посилання? – Якщо так, використовуйте Map для відстеження.
- Чи потрібна підтримка спеціальних типів? – Якщо так, використовуйте бібліотеку.
Альтернативи
structuredClone(obj)– вбудований API у сучасних браузерах і Node.js 17+.lodash.cloneDeep– перевірена бібліотека з підтримкою всіх типів.
Часті помилки
- Використання
JSON.parse(JSON.stringify(...))для об’єктів з функціями. - Необережне копіювання Date → рядок.
- Переповнення стеку при рекурсивній копії глибоких об’єктів.
- Забуття про
null–typeof null === 'object'. - Необроблені Symbol‑ключі.
- Втрата прототипу – новий об’єкт не успадковує
__proto__. - Необроблені Map/Set → порожні структури.
- Відсутність глибокої копії у бібліотеці
Object.assign.
Cheatsheet
- Shallow:
{...obj}абоObject.assign({}, obj). - Deep (JSON):
JSON.parse(JSON.stringify(obj)). - Deep (lodash):
_.cloneDeep(obj). - Deep (structuredClone):
structuredClone(obj).
Follow‑up питання
- Які недоліки має
structuredCloneу старих браузерах? - Як обробити циклічні посилання при глибокій копії?
- Чому
Object.assignне працює з Symbol‑ключами?