Поверхностное и глубокое копирование в JavaScript: что это, в чём разница и как правильно копировать объекты

Работа с объектами и массивами в JavaScript неизбежно приводит к ситуациям, когда необходимо создать копию данных. На первый взгляд задача кажется тривиальной — достаточно просто присвоить значение новой переменной. Однако именно здесь кроется первая ловушка для разработчиков: в JavaScript объекты и массивы относятся к ссылочным типам данных, а это означает, что при присваивании копируется не само значение, а лишь ссылка на область памяти, где хранится объект.
Давайте разберёмся, почему копирование становится необходимостью:
- Изменение данных без побочных эффектов — когда функция должна модифицировать объект, не затрагивая оригинал, используемый в других частях приложения.
- Работа с состоянием в React и других фреймворках — иммутабельность данных является фундаментальным принципом современных библиотек для управления состоянием.
- Создание независимых версий конфигураций — например, при тестировании различных настроек или создании шаблонов с последующей кастомизацией.
- Предотвращение race conditions — в асинхронном коде независимые копии данных помогают избежать конфликтов при параллельных операциях.
- Откат изменений — сохранение оригинальной версии данных позволяет реализовать функциональность undo/redo или валидацию с возможностью отмены.

Простая метафора для понимания ссылочного типа данных: у вас может быть два разных «брелока» (переменные), но они оба прикреплены к одному и тому же «ключу» (объекту в памяти).
Понимание механизмов копирования — это не просто теоретическое знание, а практический навык, который напрямую влияет на надёжность и предсказуемость кода.
- Поверхностное копирование (Shallow Copy)
- Глубокое копирование (Deep Copy)
- Сравнение shallow и deep copy: чем отличаются и когда какой использовать
- Реальные кейсы: когда поверхностное копирование безопасно, а когда оно приведёт к багам
- Как выбрать правильный способ копирования в зависимости от задачи
- Заключение
- Рекомендуем посмотреть курсы по JavaScript разработке
Поверхностное копирование (Shallow Copy)
Поверхностное копирование создаёт новый объект или массив, копируя значения его свойств верхнего уровня. Звучит просто, но здесь начинается самое интересное: если свойство содержит примитивное значение (строку, число, boolean), оно копируется по значению. Однако если свойство является объектом или массивом, копируется лишь ссылка на него, а не сам объект.
Представим себе ситуацию: мы создали поверхностную копию объекта и изменили вложенный массив в копии. Что произойдёт? Изменение отразится и на оригинале, поскольку оба объекта ссылаются на один и тот же массив в памяти. Это фундаментальное ограничение shallow copy, которое становится источником трудноуловимых багов.

Эта схема наглядно показывает, почему изменение вложенного объекта в копии ломает оригинал. Обе переменные имеют собственные примитивные свойства, но ссылаются на одну и ту же область памяти для вложенных данных.
Риск мутаций данных возникает именно на уровне вложенных структур. Когда мы работаем с объектом, содержащим другие объекты, массивы или функции, поверхностное копирование создаёт иллюзию независимости данных, но на практике связь между оригиналом и копией сохраняется через общие ссылки на вложенные элементы.
const original = {
name: 'John',
age: 30,
hobbies: ['reading', 'swimming']
};
// Поверхностное копирование через Object.assign
const copy1 = Object.assign({}, original);
// Поверхностное копирование через spread-оператор
const copy2 = { ...original };
copy1.hobbies.push('cooking');
console.log(original.hobbies); // ['reading', 'swimming', 'cooking']
console.log(copy2.hobbies); // ['reading', 'swimming', 'cooking']
Типичные ошибки при использовании shallow copy:
- Предположение полной независимости — разработчики ожидают, что изменения в копии никак не затронут оригинал.
- Игнорирование вложенности данных — недооценка того, насколько глубоко структурированы объекты в приложении.
- Использование shallow copy для сложных состояний — попытка скопировать многоуровневые конфигурации или состояния компонентов.
- Отсутствие проверки типов вложенных свойств — непонимание того, какие свойства являются объектами, а какие примитивами.
Возникает закономерный вопрос: если поверхностное копирование настолько ограничено, зачем оно вообще нужно? Ответ кроется в производительности и простоте использования для плоских структур данных.
Примеры поверхностного копирования
Рассмотрим практические примеры, демонстрирующие поведение shallow copy в различных сценариях.
Копирование простого объекта:
const user = {
username: 'alice',
role: 'admin',
isActive: true
};
const userCopy = { ...user };
userCopy.role = 'moderator';
console.log(user.role); // 'admin' -- оригинал не изменился
console.log(userCopy.role); // 'moderator'
В этом случае shallow copy работает идеально, поскольку все свойства объекта являются примитивными значениями.
Копирование массива:
const numbers = [1, 2, 3, 4, 5]; const numbersCopy = [...numbers]; numbersCopy.push(6); console.log(numbers); // [1, 2, 3, 4, 5] console.log(numbersCopy); // [1, 2, 3, 4, 5, 6]
Массив примитивов копируется корректно — изменения в копии не влияют на оригинал.
Копирование объекта с вложенными структурами:
const project = {
title: 'Website Redesign',
budget: 50000,
team: ['Alice', 'Bob', 'Charlie'],
config: {
theme: 'dark',
language: 'en'
}
};
const projectCopy = Object.assign({}, project);
projectCopy.team.push('Diana');
projectCopy.config.theme = 'light';
console.log(project.team); // ['Alice', 'Bob', 'Charlie', 'Diana']
console.log(project.config.theme); // 'light'
Здесь мы наблюдаем проблему: изменения в массиве team и объекте config затронули оригинальный объект. Именно в таких ситуациях поверхностное копирование становится источником ошибок.
Проблемы поверхностного копирования: когда оно ломает данные
Давайте рассмотрим реальный сценарий, который часто встречается в разработке и демонстрирует, как shallow copy может привести к неожиданным багам.
Представим ситуацию: мы разрабатываем форму редактирования профиля пользователя. При открытии формы необходимо создать копию данных пользователя, чтобы изменения применялись только после нажатия кнопки «Сохранить», а при отмене — данные оставались неизменными.
const userProfile = {
name: 'Анна Иванова',
email: 'anna@example.com',
settings: {
notifications: true,
theme: 'light',
privacy: {
showEmail: false,
showPhone: true
}
}
};
// Создаём "копию" для редактирования
const editableProfile = { ...userProfile };
// Пользователь меняет настройки в форме
editableProfile.settings.notifications = false;
editableProfile.settings.privacy.showEmail = true;
// Пользователь нажимает "Отмена"
console.log(userProfile.settings.notifications); // false -- данные изменились!
console.log(userProfile.settings.privacy.showEmail); // true -- оригинал испорчен!
Что произошло? Мы создали поверхностную копию, но вложенный объект settings и его собственный вложенный объект privacy остались общими между оригиналом и копией. Функция отмены изменений не сработала, потому что «копия» на самом деле изменяла оригинальные данные.
Такие баги особенно коварны, поскольку проявляются не сразу и только при определённых действиях пользователя. В production-среде подобная ошибка может привести к потере пользовательских данных или некорректному отображению интерфейса.
Глубокое копирование (Deep Copy)
Глубокое копирование решает фундаментальную проблему поверхностного копирования, создавая полностью независимую копию объекта со всеми его вложенными структурами. При deep copy рекурсивно копируются все уровни вложенности — каждый объект, массив и их содержимое воссоздаётся заново в памяти.
В отличие от shallow copy, где вложенные объекты сохраняют ссылки на оригинальные данные, глубокое копирование разрывает все связи между копией и оригиналом. Это означает, что любые изменения в копии, независимо от уровня вложенности, никак не повлияют на исходный объект, и наоборот.
Возникает вопрос: если deep copy настолько надёжен, почему бы не использовать его всегда? Ответ кроется в производительности и сложности реализации. Глубокое копирование требует больше ресурсов, особенно для габаритных структур данных, и не всегда необходимо для решения конкретной задачи.
Сравнение поведения shallow vs deep:
const data = {
user: 'John',
permissions: {
read: true,
write: false,
admin: {
fullAccess: false
}
}
};
// Поверхностная копия
const shallowCopy = { ...data };
shallowCopy.permissions.admin.fullAccess = true;
console.log(data.permissions.admin.fullAccess); // true -- оригинал изменён!
// Глубокая копия
const deepCopy = structuredClone(data);
deepCopy.permissions.admin.fullAccess = true;
console.log(data.permissions.admin.fullAccess); // false -- оригинал не тронут
Ключевое различие очевидно: при shallow copy изменение вложенного свойства fullAccess затрагивает оригинал, поскольку объект permissions и все его вложенные структуры остаются общими. При deep copy создаётся полностью независимая структура данных.
Глубокое копирование становится необходимостью в ситуациях, когда мы работаем с комплексными объектами состояния, конфигурациями приложений, данными из API с многоуровневой вложенностью или когда требуется полная изоляция данных для параллельной обработки. Понимание того, когда применять deep copy, а когда достаточно shallow copy, — это признак зрелости разработчика.
Способы сделать глубокую копию: обзор всех вариантов
Современный JavaScript предлагает несколько подходов к реализации глубокого копирования, каждый из которых имеет свои преимущества и ограничения. Давайте рассмотрим основные методы.
structuredClone() — встроенный надёжный способ
Это относительно новый нативный метод JavaScript, появившийся в современных браузерах и Node.js. Метод structuredClone() использует структурированный алгоритм клонирования, который корректно обрабатывает большинство типов данных, включая Date, RegExp, Map, Set и даже циклические ссылки.
const original = {
date: new Date(),
map: new Map([['key', 'value']]),
nested: { deep: { value: 42 } }
};
const clone = structuredClone(original);
Это наиболее рекомендуемый способ для большинства задач, когда доступна современная среда выполнения.
Рекурсивная функция deepCopy() — настраиваемый вариант
Классический подход, дающий полный контроль над процессом копирования. Рекурсивная функция позволяет добавить собственную логику обработки специфических типов данных.
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
Этот метод хорош для образовательных целей и ситуаций, когда необходима кастомизация процесса клонирования.
Lodash _.cloneDeep() — универсальная библиотека
Библиотека Lodash предоставляет надёжную реализацию глубокого копирования, проверенную временем и используемую в тысячах проектов.
import _ from 'lodash';
const original = { /* сложная структура */ };
const clone = _.cloneDeep(original);
Преимущество этого подхода — в его зрелости и способности корректно обрабатывать edge cases, которые могут быть упущены в самописных решениях.
JSON.parse(JSON.stringify()) — быстрый способ с ограничениями
Самый простой и быстрый метод, основанный на сериализации объекта в JSON-строку и последующей десериализации. Однако этот подход имеет существенные ограничения.
const original = {
name: 'Alice',
data: { values: [1, 2, 3] }
};
const clone = JSON.parse(JSON.stringify(original));
Метод не работает с функциями, undefined, Symbol, Date (преобразуется в строку), циклическими ссылками и другими специфическими типами. Несмотря на ограничения, для простых объектов с примитивными значениями и массивами это самый производительный вариант.
Выбор метода зависит от конкретной задачи, структуры данных и требований к производительности. В современной разработке рекомендуется начинать с structuredClone(), а при необходимости использовать альтернативные подходы.
Ограничения каждого способа глубокого копирования
Понимание ограничений различных методов глубокого копирования критически важно для выбора правильного инструмента под конкретную задачу. Давайте систематизируем информацию в виде таблицы для удобства сравнения.
| Метод | Плюсы | Минусы | Где применять |
|---|---|---|---|
| structuredClone() | Нативный метод, не требует зависимостей; корректно обрабатывает Date, RegExp, Map, Set; поддерживает циклические ссылки | Не копирует функции и Symbol; относительно новый (может отсутствовать в старых браузерах); не работает с DOM-узлами | Современные приложения с комплексными структурами данных; когда важна надёжность и не требуется копирование функций |
| Рекурсивная функция | Полный контроль над логикой; можно кастомизировать под специфические типы; образовательная ценность | Требует тщательной реализации; легко упустить edge cases; не обрабатывает циклические ссылки без доработки; производительность зависит от реализации | Учебные проекты; ситуации с нестандартными требованиями к копированию; когда нужна специфическая обработка типов |
| Lodash _.cloneDeep() | Проверенное решение; обрабатывает множество edge cases; хорошая производительность; поддерживает циклические ссылки | Требует подключения внешней библиотеки; увеличивает размер бандла; избыточность для простых задач | Большие enterprise-проекты; когда Lodash уже используется в проекте; сложные структуры с разнообразными типами данных |
| JSON.parse(JSON.stringify()) | Максимально простой синтаксис; высокая производительность для совместимых данных; не требует зависимостей | Теряет функции, undefined, Symbol; Date превращается в строку; не работает с циклическими ссылками (выбрасывает ошибку); игнорирует неперечисляемые свойства | Простые объекты с примитивами и массивами; API-данные в формате JSON; когда гарантированно нет несовместимых типов |
Рассмотрим практический пример ограничений JSON-метода:
const problematic = {
a: undefined,
method: () => { console.log('hello') },
symbol: Symbol("foo"),
date: new Date()
};
const copy = JSON.parse(JSON.stringify(problematic));
console.log(copy);
// { date: "2025-12-13T..." }
// Потеряны: undefined, функция, Symbol
Как видим, метод JSON подходит далеко не для всех сценариев. Исследования показывают, что наиболее частая ошибка разработчиков — использование JSON.parse(JSON.stringify()) без проверки типов данных в копируемом объекте, что приводит к молчаливой потере информации.
Сравнение shallow и deep copy: чем отличаются и когда какой использовать
Выбор между поверхностным и глубоким копированием — это не просто технический вопрос, а стратегическое решение, влияющее на архитектуру приложения, его производительность и надёжность. Давайте систематизируем ключевые различия и сформулируем практические рекомендации.
Сводная таблица различий:
| Критерий | Shallow Copy | Deep Copy |
|---|---|---|
| Глубина копирования | Только первый уровень свойств | Все уровни вложенности рекурсивно |
| Вложенные объекты | Копируются по ссылке | Создаются новые независимые копии |
| Независимость данных | Частичная (только примитивы верхнего уровня) | Полная на всех уровнях |
| Производительность | Высокая, O(n) где n — количество свойств первого уровня | Ниже, зависит от глубины вложенности |
| Потребление памяти | Минимальное | Выше, пропорционально размеру структуры |
| Риск мутаций | Высокий при наличии вложенных структур | Отсутствует |
| Типичные методы | {…obj}, Object.assign(), […arr] | structuredClone(), _.cloneDeep(), рекурсия |
Как выбрать метод под задачу
Используйте shallow copy когда:
- Объект содержит только примитивные значения на верхнем уровне.
- Производительность критична, а данные простые.
- Работаете с React props, где гарантированно нет вложенности.
- Создаёте копию массива примитивов для сортировки или фильтрации.
Используйте deep copy когда:
- Объект имеет многоуровневую вложенность.
- Требуется полная изоляция данных (формы редактирования, undo/redo).
- Работаете с состоянием Redux или другими state managers.
- Копируете конфигурационные объекты с вложенными настройками.
- Необходимо предотвратить любые побочные эффекты.
Наш опыт показывает, что наиболее частая ошибка — использование shallow copy «по умолчанию» без анализа структуры данных. Разработчики привыкают к spread-оператору и применяют его везде, не задумываясь о последствиях. Правильный подход — всегда начинать с анализа данных: какова их структура, есть ли вложенность, какие гарантии нужны от копирования.
Реальные кейсы: когда поверхностное копирование безопасно, а когда оно приведёт к багам
Теория становится по-настоящему ценной, когда мы видим её применение в реальных сценариях разработки. Рассмотрим практические примеры, с которыми сталкивается каждый JavaScript-разработчик.
Кейс 1: Корзина товаров в интернет-магазине
Представим типичную ситуацию — пользователь добавляет товары в корзину, и нам нужно обновить общую стоимость при изменении количества.
const cart = {
items: [
{ id: 1, name: 'Ноутбук', price: 50000, quantity: 1 },
{ id: 2, name: 'Мышь', price: 1500, quantity: 2 }
],
total: 53000
};
// Неправильно: shallow copy
const updatedCart = { ...cart };
updatedCart.items[0].quantity = 2;
console.log(cart.items[0].quantity); // 2 -- оригинал испорчен!
Массив items скопировался по ссылке, и изменение количества товара повлияло на оригинальную корзину. В production это может привести к дублированию заказов или некорректному отображению данных.
Кейс 2: Конфигурационные объекты приложения
При работе с настройками приложения часто требуется создавать временные конфигурации для тестирования или A/B-тестирования.
const appConfig = {
api: {
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
},
features: {
darkMode: true,
notifications: false
}
};
// Попытка создать тестовую конфигурацию
const testConfig = { ...appConfig };
testConfig.api.timeout = 1000;
testConfig.api.headers['Authorization'] = 'Bearer test-token';
// Оригинальная конфигурация теперь сломана!
console.log(appConfig.api.timeout); // 1000
Кейс 3: Безопасное использование shallow copy
Не всё так мрачно — существуют сценарии, где поверхностное копирование работает идеально:
// Обновление простых свойств пользователя
const user = { id: 1, name: 'Алексей', age: 28, role: 'developer' };
const updatedUser = { ...user, age: 29 };
// Безопасно: все свойства -- примитивы
// Фильтрация массива примитивов
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = [...numbers].filter(n => n % 2 === 0);
// Безопасно: массив содержит только числа
Типичные ошибки новичков: начинающие разработчики часто не различают примитивы и ссылочные типы внутри структур данных. Они видят, что { …obj } работает в простых случаях, и начинают применять этот паттерн везде. Другая распространённая ошибка — копирование объектов из Redux store с помощью spread-оператора при наличии вложенных массивов, что приводит к мутациям состояния и непредсказуемому поведению приложения.
Как выбрать правильный способ копирования в зависимости от задачи
Выбор метода копирования — это баланс между надёжностью, производительностью и сложностью реализации. Давайте сформулируем практические рекомендации, которые помогут принимать обоснованные решения в различных ситуациях.
Рекомендации по выбору метода:
- Анализируйте структуру данных перед копированием. Первый шаг — понять, с чем вы работаете. Если объект содержит только примитивные значения на верхнем уровне, shallow copy будет оптимальным выбором. При наличии вложенных объектов или массивов необходимо глубокое копирование.
- Учитывайте размер данных и частоту операций. Для небольших объектов разница в производительности между shallow и deep copy незначительна. Однако если копирование происходит в цикле или обрабатываются большие структуры данных (например, списки с тысячами элементов), производительность становится критичной. В таких случаях рассмотрите возможность рефакторинга архитектуры данных для минимизации необходимости копирования.
- Оценивайте типы данных внутри структуры. Если объект содержит функции, Date, Map, Set или другие специфические типы, метод JSON.parse(JSON.stringify()) не подойдёт. В современных проектах используйте structuredClone(), в legacy-коде — Lodash или собственную реализацию.
- Определите требования к изоляции данных. Для форм редактирования, реализации undo/redo, работы с временными состояниями — всегда используйте deep copy. Для передачи props в React-компоненты или создания производных данных без модификации — достаточно shallow copy.
- Рассмотрите альтернативу копированию. Иногда лучшее решение — вообще избежать копирования. Иммутабельные библиотеки вроде Immer позволяют работать с данными через прокси-объекты, создавая копии только изменённых частей структуры. Это даёт баланс между удобством использования и производительностью.

Диаграмма демонстрирует разницу в скорости работы методов. Поверхностное копирование (spread) работает значительно быстрее, в то время как надежные методы глубокого копирования (structuredClone, Lodash) требуют больше ресурсов.
Лучшие практики:
- В React и других фреймворках с иммутабельным состоянием предпочитайте встроенные механизмы (например, useState с функциональными обновлениями)
- Для API-данных используйте structuredClone() или JSON-метод, в зависимости от типов данных.
- При работе с формами создавайте глубокую копию при инициализации и работайте с ней до момента сохранения.
- Документируйте в комментариях, почему выбран конкретный метод копирования, особенно если это неочевидно.
- Используйте TypeScript для контроля типов и выявления потенциальных проблем на этапе разработки.
Помните: не существует универсального решения, подходящего для всех случаев. Осознанный выбор метода копирования — это признак профессионализма и понимания внутренних механизмов JavaScript.
Заключение
Подведём итоги и сформулируем основные тезисы, которые помогут уверенно работать с копированием данных в JavaScript:
- Поверхностное копирование создаёт новый объект только на верхнем уровне. Вложенные структуры продолжают ссылаться на оригинальные данные.
- Глубокое копирование полностью разрывает связи между копией и исходным объектом. Это защищает данные от непреднамеренных мутаций.
- Shallow copy подходит для простых структур без вложенных объектов. В сложных состояниях он часто становится причиной скрытых багов.
- Deep copy необходим при работе с формами, состоянием приложений и конфигурациями. Он обеспечивает изоляцию данных и предсказуемость поведения кода.
- У каждого способа глубокого копирования есть ограничения. Выбор метода зависит от типов данных, среды выполнения и требований к производительности.
- Осознанный подход к копированию объектов в JavaScript снижает риск ошибок. Понимание различий между shallow и deep copy повышает надёжность приложений.
Если вы только начинаете осваивать профессию JavaScript-разработчика или хотите лучше разобраться в работе с данными, рекомендуем обратить внимание на подборку курсов по JavaScript. В них есть теоретическая и практическая часть, которая помогает закрепить понимание копирования объектов и работы со структурой данных в реальных задачах.
Рекомендуем посмотреть курсы по JavaScript разработке
| Курс | Школа | Цена | Рассрочка | Длительность | Дата начала | Ссылка на курс |
|---|---|---|---|---|---|---|
|
Fullstack-разработчик на JavaScript
|
Eduson Academy
100 отзывов
|
Цена
Ещё -5% по промокоду
147 000 ₽
|
От
12 250 ₽/мес
0% на 24 месяца
|
Длительность
9 месяцев
|
Старт
в любое время
|
Подробнее |
|
Автоматизированное тестирование веб-приложений на JavaScript
|
Skillbox
218 отзывов
|
Цена
Ещё -47% по промокоду
48 408 ₽
64 548 ₽
|
От
4 034 ₽/мес
Без переплат на 1 год.
5 379 ₽/мес
|
Длительность
4 месяца
|
Старт
3 февраля
|
Подробнее |
|
Полный курс по JavaScript - С нуля до результата!
|
Stepik
33 отзыва
|
Цена
2 990 ₽
|
От
748 ₽/мес
|
Длительность
1 неделя
|
Старт
в любое время
|
Подробнее |
|
Backend-разработка на Node.js
|
Нетология
46 отзывов
|
Цена
с промокодом kursy-online
28 500 ₽
50 000 ₽
|
От
2 500 ₽/мес
Без переплат на 1 год.
|
Длительность
6 месяцев
|
Старт
в любое время
|
Подробнее |
|
Профессия Fullstack-разработчик на Python
|
Skillbox
218 отзывов
|
Цена
Ещё -20% по промокоду
146 073 ₽
292 147 ₽
|
От
4 296 ₽/мес
|
Длительность
12 месяцев
|
Старт
3 февраля
|
Подробнее |
Как выбрать JavaScript-фреймворк: полный гид по React, Angular, Vue и Svelte
Выбор JavaScript-фреймворка может быть непростым. В статье сравниваются React, Angular, Vue и Svelte, их особенности, плюсы и минусы.
Тестирование гипотез: что это, зачем нужно и как правильно проводить
Что такое тестирование гипотез и зачем оно нужно бизнесу? В этой статье вы узнаете, как формулировать проверяемые предположения, приоритизировать их и выбирать правильные методы проверки.
Что такое система дистанционного обучения (СДО)
Хотите понять, что такое СДО и зачем они нужны бизнесу и образованию? В статье вы найдете объяснение принципов работы систем дистанционного обучения, их преимущества и реальные примеры внедрения.