Копирование объектов в JavaScript

Копирование объекта нужно, когда мы хотим получить новый объект с такими же свойствами, но не менять исходный.

let user = {
  name: 'Анна',
  age: 28,
};

let copy = { ...user };

copy.age = 29;

console.log(user.age); // 28
console.log(copy.age); // 29

Объекты относятся к ссылочным типам данных, поэтому с ними важно отличать копию от обычной ссылки.

Что значит скопировать объект

Скопировать объект - значит создать новый объект и перенести в него свойства старого объекта.

let user = {
  name: 'Анна',
  city: 'Москва',
};

let copy = { ...user };

console.log(copy); // { name: 'Анна', city: 'Москва' }

После этого можно менять копию, не затрагивая исходный объект.

Присваивание не копирует объект

Если просто записать объект в другую переменную, копия не создается.

let user = {
  name: 'Анна',
  age: 28,
};

let copy = user;

copy.age = 29;

console.log(copy.age); // 29
console.log(user.age); // 29

В переменной copy лежит ссылка на тот же объект. Поэтому изменение через copy видно и в user.

Это нормально для JavaScript, но может удивлять, если ожидать независимую копию.

Копирование через spread

Самый частый способ скопировать простой объект - использовать spread-оператор .

let user = {
  name: 'Анна',
  age: 28,
};

let copy = { ...user };

console.log(copy); // { name: 'Анна', age: 28 }

Фигурные скобки создают новый объект, а …user переносит в него свойства из старого объекта.

Такой способ хорошо читается и часто используется в современном коде.

Копирование через Object.assign()

Еще один способ - метод Object.assign().

let user = {
  name: 'Анна',
  age: 28,
};

let copy = Object.assign({}, user);

console.log(copy); // { name: 'Анна', age: 28 }

Первым аргументом передается объект, в который нужно скопировать свойства. В примере это пустой объект .

Вторым аргументом передается объект, который копируем.

Object.assign({}, user);

В современном коде для простого копирования чаще используют spread, но Object.assign() тоже встречается.

Копия и изменение объекта

Копирование удобно, когда нужно получить обновленный объект и оставить старый без изменений.

let user = {
  name: 'Анна',
  age: 28,
};

let updatedUser = {
  ...user,
  age: 29,
};

console.log(user.age); // 28
console.log(updatedUser.age); // 29

Сначала в новый объект попадают свойства из user, а потом свойство age перезаписывается новым значением.

Такой подход часто используют, когда данные нельзя менять напрямую.

Поверхностная копия

Spread и Object.assign() создают поверхностную копию объекта.

Если в объекте лежат строки, числа и булевы значения, обычно этого достаточно.

let user = {
  name: 'Анна',
  age: 28,
};

let copy = { ...user };

copy.name = 'Мария';

console.log(user.name); // Анна
console.log(copy.name); // Мария

Но если внутри объекта есть другой объект, копируется ссылка на него.

let user = {
  name: 'Анна',
  contacts: {
    email: 'anna@example.com',
  },
};

let copy = { ...user };

copy.contacts.email = 'new@example.com';

console.log(user.contacts.email); // new@example.com

Объект copy новый, но вложенный объект contacts остался общим для user и copy.

Если нужно изменить вложенный объект независимо, его тоже нужно скопировать.

let user = {
  name: 'Анна',
  contacts: {
    email: 'anna@example.com',
  },
};

let copy = {
  ...user,
  contacts: {
    ...user.contacts,
  },
};

copy.contacts.email = 'new@example.com';

console.log(user.contacts.email); // anna@example.com
console.log(copy.contacts.email); // new@example.com

Глубокая копия через structuredClone()

Если нужно скопировать объект вместе с вложенными объектами, можно использовать structuredClone().

let user = {
  name: 'Анна',
  contacts: {
    email: 'anna@example.com',
  },
};

let copy = structuredClone(user);

copy.contacts.email = 'new@example.com';

console.log(user.contacts.email); // anna@example.com
console.log(copy.contacts.email); // new@example.com

structuredClone() создает глубокую копию: вложенные объекты тоже становятся новыми объектами.

Но использовать его для каждого копирования не нужно. Для простых объектов чаще достаточно spread. Также structuredClone() не подходит для функций и некоторых специальных значений.

Что выбрать

Для обычного копирования простого объекта чаще всего используют spread.

let copy = { ...object };

Если в проекте уже используют Object.assign(), этот вариант тоже нормальный.

let copy = Object.assign({}, object);

Если нужно скопировать вложенную часть объекта, можно скопировать ее отдельно.

let copy = {
  ...user,
  contacts: {
    ...user.contacts,
  },
};

Если нужна глубокая копия всей структуры, можно рассмотреть structuredClone().

Итого

  • let copy = object не копирует объект, а записывает ссылку.
  • { …object } - частый способ скопировать простой объект.
  • Object.assign({}, object) тоже создает новый объект.
  • Spread и Object.assign() создают поверхностную копию.
  • Вложенные объекты при поверхностном копировании остаются общими.
  • Для глубокой копии можно использовать structuredClone(), если он подходит под задачу.