Копирование объекта
Представьте картотеку. Анкета лежит в ящике, а в переменной записан только номер этого ящика. Если переписать номер на второй листок, анкет не станет две: оба листка ведут к одному ящику.
Присваивание объекта другой переменной работает так же. Второй объект не появляется, и изменение через новое имя видно через старое:
const settings = { size: "M", color: "black" };
const other = settings;
other.size = "L";
console.log(settings.size); // L
console.log(other === settings); // trueДавайте разберём пример по строкам:
const other = settingsкопирует не объект, а номер ящика.other.size = "L"меняет анкету в этом ящике.settings.sizeчитает ту же анкету и печатаетL.other === settingsдаётtrue: оба имени ведут к одному объекту.
Как и с массивами, переменная хранит не сам объект, а ссылку на него. Оператор === на объектах проверяет, один ли это объект, а не одинаковы ли свойства. Поэтому { a: 1 } === { a: 1 } даёт false: это два разных объекта.
Синтаксис расширения и Object.assign
Чтобы получить вторую анкету, нужна ксерокопия: новый объект с теми же свойствами. Для этого есть два способа.
Три точки
Синтаксис расширения { ...settings } создаёт новый объект с теми же свойствами. Если ключ встречается дважды, остаётся значение, записанное позже. Поэтому:
{ ...settings, size: "L" }— копия с другим размером;- в
{ size: "L", ...settings }размер"L"заменится скопированным"M".
Object.assign
Функция Object.assign(target, source) копирует свойства source в объект target и возвращает target. Источников может быть несколько, они копируются по очереди. С пустым объектом {} первым аргументом она тоже даёт копию:
const settings = { size: "M", color: "black" };
const a = { ...settings };
const b = Object.assign({}, settings);
const c = { ...settings, size: "L" };
a.color = "red";
console.log(a === settings, settings.color); // false black
console.log(b); // { size: 'M', color: 'black' }
console.log(c); // { size: 'L', color: 'black' }Разберём по строкам:
a— копия через три точки,b— черезObject.assignс пустым{}.c— копия, в которой размер сразу заменён на"L".a.color = "red"меняет только копию:a === settingsдаётfalse, аsettings.colorостаётсяblack.bиcпечатаются как отдельные объекты со своими свойствами.
Вложенные объекты
Синтаксис расширения и Object.assign копируют только свойства верхнего уровня. Если значение свойства — объект, в копию попадает ссылка на него, и вложенный объект остаётся общим. Такая копия называется поверхностной.
В картотеке это выглядит так. В строке «Адрес» анкеты записан не сам адрес, а номер карточки с адресом. Ксерокопия переписывает этот номер, и обе анкеты ведут к одной карточке.
Функция structuredClone копирует все уровни и даёт глубокую копию: она переписывает и саму карточку с адресом.
const user = { name: "Ann", address: { city: "Oslo" } };
const shallow = { ...user };
const deep = structuredClone(user);
user.address.city = "Rome";
console.log(shallow.address.city); // Rome
console.log(deep.address.city); // OsloИзменение user.address.city видно через shallow, потому что оба объекта хранят ссылку на один и тот же адрес. deep получил собственный адрес, и его город остался прежним.
Пройдите программу по шагам и следите за city в трёх объектах: после замены города он меняется у user и shallow сразу, а у deep — нет.
1const user = {2 name: "Ann",3 address: { city: "Oslo" }4};5const shallow = { ...user };6const deep = structuredClone(user);7 8shallow.name = "Bob";9console.log(user.name);10 11user.address.city = "Rome";12console.log(shallow.address.city);13console.log(deep.address.city);
user — объект с вложенным объектом address.
Здесь аналогия с ксерокопией перестаёт работать. Бумажная копия никогда не меняется вслед за оригиналом. А у поверхностной копии меняется всё, что лежит во вложенных объектах, потому что они общие.
Разницу между поверхностной и глубокой копией часто спрашивают на собеседовании. Короткий ответ: три точки и Object.assign копируют один уровень, а structuredClone — все.
Что выбрать
Несколько правил, которые помогают выбрать способ:
structuredCloneкопирует и массивы, и объекты внутри массивов;- массив внутри объекта ведёт себя как вложенный объект: синтаксис расширения копирует ссылку на него, а не элементы;
- поверхностной копии хватает, когда значения свойств — числа, строки и другие примитивы;
structuredCloneне может скопировать метод: на объекте с методом программа останавливается с ошибкойDataCloneError.
Частая ошибка
Первым аргументом Object.assign передали объект, который нужно сохранить:
const defaults = { size: "M", color: "black" };
const mine = Object.assign(defaults, { size: "L" }); // ✗
console.log(mine.size);Программа выводит L, но defaults.size теперь тоже "L", и mine === defaults. Object.assign меняет свой первый аргумент и возвращает его же.
Новый объект получится, если первым передать {}: Object.assign({}, defaults, { size: "L" }). Ещё один способ — запись { ...defaults, size: "L" }.
Главное
- Присваивание копирует ссылку: оба имени ведут к одному объекту, а
===проверяет, один ли это объект. { ...obj }иObject.assign({}, obj)дают поверхностную копию: вложенные объекты остаются общими.structuredClone(obj)даёт глубокую копию, но не копирует методы.Object.assignменяет свой первый аргумент, поэтому для копии первым передают{}.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.