Копирование объектов
Присваивание не создаёт копию объекта:
const user = { name: 'Ира', age: 20,};const copy = user;После этого user и copy ведут к одному объекту:
Поэтому изменение через copy видно и через user:
copy.name = 'Макс';console.log(user.name);Но иногда нужен другой результат: взять данные существующего объекта и получить новый объект, который можно менять независимо.
Самый прямой способ – создать новый литерал и перенести свойства вручную:
const user = { name: 'Ира', age: 20,};const copy = { name: user.name, age: user.age,};copy.name = 'Макс';console.log(user.name);console.log(copy.name);Литерал { ... } выполнился ещё раз, поэтому объектов теперь два:
Теперь user и copy – два разных объекта. У каждого свои свойства, поэтому изменение copy. меняет только copy и никак не влияет на user.
Почему это работает
Сравните две записи:
const copy = user;const copy = { name: user.name, age: user.age,};В первом случае нового объекта нет вообще – копируется ссылка. Во втором выполняется новый литерал { ... }: JavaScript создаёт новый объект и записывает в него значения свойств user. Именно новый объект делает копию независимой от оригинала.
Для двух свойств такой код ещё терпим:
const copy = { name: user.name, age: user.age,};А если свойств десять?
const profile = { name: 'Ира', age: 20, city: 'Москва', role: 'student', active: true,};Переписывать каждое свойство вручную неудобно, тем более что состав объекта со временем меняется. Нужен способ пройти по свойствам автоматически.
Цикл for...in позволяет перебрать свойства объекта. На каждой итерации key получает имя очередного свойства.
const user = { name: 'Ира', age: 20, city: 'Москва',};const copy = {};for (const key in user) { copy[key] = user[key];}console.log(copy);Разберём центральную строку:
copy[key] = user[key];Сначала key равен 'name', и строка по смыслу превращается в:
copy['name'] = user['name'];Потом key равен 'age':
copy['age'] = user['age'];Так пустой объект по одному получает те же свойства.
copy. Свойств в нём пока нет.Копировать свойства вручную через цикл можно, но такая задача встречается часто. Поэтому в JavaScript есть готовый метод Object.. Он берёт свойства одного или нескольких объектов и записывает их в другой объект:
Object.assign(target, source1, source2);Первый аргумент – объект, который будет изменён: именно в него попадут свойства. Все следующие аргументы – объекты, из которых свойства берутся.
Например, добавим настройки звука в уже существующий объект:
const settings = { theme: 'dark',};const sound = { volume: 50,};Object.assign(settings, sound);console.log(settings);После вызова settings содержит и своё старое свойство theme, и новое свойство volume, взятое из sound.
Важно: Object. здесь не создавал второй объект. Он изменил уже существующий settings.
Object. assign возвращает изменённый объект
У Object. есть ещё одна особенность: результатом вызова становится тот же объект, который стоял первым аргументом. Вызов Object. сначала добавляет volume в settings, а затем возвращает сам settings:
const settings = { theme: 'dark',};const same = settings;const result = Object.assign(settings, { volume: 70 });console.log(same.volume);console.log(result === settings);same, result и settings ведут к одному объекту. Никакой копии в этом примере не создавалось.
Как с помощью Object. assign получить копию
Вызов Object. нужен прежде всего для изменения существующего объекта. Но если первым аргументом передать новый пустой объект {}, свойства запишутся уже в него, а исходный объект останется нетронутым.
Возьмём объект user и вместо существующего объекта первым аргументом передадим новый пустой литерал:
const user = { name: 'Ира', age: 20,};const copy = Object.assign({}, user);Object. записывает свойства user в новый объект {} и возвращает этот новый объект – поэтому его и получает переменная copy.
По смыслу происходит то же, что мы только что делали циклом: свойства user одно за другим записываются в новый объект. Object. просто выполняет эту работу за нас.
Проверим:
copy.name = 'Макс';console.log(user.name);console.log(copy.name);console.log(user === copy);Теперь user и copy – два разных объекта. Изменение свойства одного не меняет такое же свойство другого.
Несколько объектов за один вызов
Object. может брать свойства не только из одного объекта. Все аргументы после первого обрабатываются по очереди:
const base = { name: 'Ира', age: 20,};const extra = { city: 'Москва',};const user = Object.assign({}, base, extra);console.log(user);Сначала в новый {} попадут свойства base, затем – свойства extra. Так удобно собирать один объект из нескольких частей.
Если свойства совпадают
Объекты обрабатываются слева направо. Если несколько из них содержат свойство с одинаковым именем, значение из более позднего объекта перезапишет предыдущее:
const user = Object.assign( {}, { name: 'Ира', age: 20 }, { age: 21 });console.log(user);Сначала age получил значение 20, а следующий объект снова записал свойство age – уже со значением 21. Остаётся значение, записанное позже: 20 → 21.
Object. умеет и изменять существующие объекты, и создавать копии через пустой {}. Но для простого копирования запись выглядит громоздко:
const copy = Object.assign({}, user);В современном JavaScript для этого чаще используют более короткую запись: новый объект создают обычным литералом, а свойства старого переносят внутрь него.
Для этого используется синтаксис ... – spread:
const user = { name: 'Ира', age: 20,};const copy = { ...user,};Фигурные скобки создают новый объект, а ...user переносит внутрь него свойства user.
Запись можно читать почти буквально: «создай новый объект и скопируй в него свойства user».
Объекты разные, и изменение копии не меняет оригинал:
console.log(user === copy);copy.age = 21;console.log(user.age);console.log(copy.age);Что именно делает ...user
Результат записи { ...user } можно представить так:
const copy = { ...user,};const copy = { name: user.name, age: user.age,};Spread не клонирует объект каким-то особым образом – он переносит его свойства в новый литерал. Новый объект появляется благодаря { }, а ...user определяет, какие свойства в него положить.
Spread особенно удобен, когда нужно взять почти готовый объект, но изменить несколько свойств:
const user = { name: 'Ира', age: 20, city: 'Москва',};const updated = { ...user, age: 21,};console.log(updated);console.log(user.age);В updated сначала попадают все свойства user: name, age и city. Затем строка age: 21 записывает age ещё раз – остаётся последнее значение. Исходный объект при этом не изменился.
Общий вид приёма: сначала ...user, затем свойства, которые нужно добавить или заменить.
Порядок имеет значение
const user = { name: 'Ира', age: 20, city: 'Москва',};Две записи ниже дают разный результат:
const a = { ...user, age: 100,};console.log(a.age);const b = { age: 100, ...user,};console.log(b.age);В a свойство age записано последним, поэтому там 100. В b сначала записывается 100, но затем ...user снова переносит исходный age, и остаётся 20. Правило простое: если свойство записали несколько раз, остаётся последнее значение.
Мы разобрали три способа создать новый объект на основе уже существующего:
// вручнуюconst copy1 = {};for (const key in user) { copy1[key] = user[key];}// Object.assignconst copy2 = Object.assign({}, user);// spreadconst copy3 = { ...user };Во всех наших простых примерах результат один: появляется новый объект, в который копируются свойства user. Различается только способ записи:
- цикл делает копирование вручную – мы сами берём свойства одно за другим;
Object.делает эту работу готовым методом;assign - spread
...позволяет записать ту же задачу короче и нагляднее прямо внутри нового объекта.
Поэтому дальше для обычного копирования мы чаще будем использовать spread:
const copy = { ...user };Его удобно не только копировать, но и сразу дополнять или изменять:
const updatedUser = { ...user, age: 21, city: 'Москва',};При этом понимать Object. всё равно нужно: в уже написанном JavaScript-коде он встречается часто.
// теперь вы знаете
const copy = userкопирует ссылку, а не объект: новый объект появляется там, где выполняется литерал{ … }for...inперебирает имена свойств, аcopy[записывает соответствующие значения в новый объектkey] = user[ key] Object.записывает свойства в объект из первого аргумента и возвращает этот объект;assign Object.даёт новый объект с теми же свойствамиassign({}, user) - Spread
{ ...user }делает то же короче, а{ ...user, age: 21 }даёт копию с изменением - Если свойство записано в литерале несколько раз, остаётся последнее значение – поэтому порядок важен
Верно ли, что после строки book. и same., и copy. равны 500?
const book = {title: 'Дюна',pages: 412,};const same = book;const copy = {title: book.title,pages: book.pages,};book.pages = 500;
Верно ли, что после вызова в объекте profile появилось свойство city?
const profile = { name: 'Ира' };const address = { city: 'Казань' };Object.assign(address, profile);
Что напечатает код?
const saved = { size: 16 };const defaults = { theme: 'light', size: 14 };const settings = Object.assign({}, saved, defaults);console.log(settings.size);
Какая запись создаёт такой же объект, как { ...product }: новый, с теми же свойствами и значениями?
const product = {title: 'Лампа',price: 1200,};
Что появится в консоли?
const order = {id: 7,status: 'new',total: 500,};const paid = {status: 'paid',...order,total: 450,};console.log(paid.status);console.log(paid.total);
Что появится в консоли?
const user = { name: 'Ира' };const a = { ...user };const b = Object.assign({}, user);const c = Object.assign(user, {});a.name = 'Макс';b.name = 'Лена';c.name = 'Ян';console.log(user.name);