Ссылки и сравнение объектов
С числами, строками и другими простыми значениями присваивание ведёт себя привычно: значение можно положить во вторую переменную, а затем менять обе независимо:
let a = 5;let b = a;b = 6;console.log(a);console.log(b);Когда выполнилось let b = a, значение 5 скопировалось в b. После этого у каждой переменной своё значение: в a лежит 5, в b – 6. С объектами похожая запись приводит к неожиданному результату:
const user = { name: 'Ира',};const admin = user;admin.name = 'Макс';console.log(user.name);Мы меняли admin.. Почему же изменился user.?
Когда выполняется литерал { name: 'Ира' }, JavaScript создаёт один объект. Для понимания удобно представлять, что переменная user хранит ссылку на него – возможность обратиться именно к этому объекту.
Теперь выполним const admin = user. Новый объект здесь не создаётся: в admin попадает ссылка на тот же самый объект. Поэтому строка admin. не изменяет какой-то отдельный объект admin – она идёт по ссылке к общему объекту и меняет его. После этого и user., и admin. читают одно и то же свойство одного объекта.
Если одной переменной присвоили другую переменную с объектом, обе указывают на один объект.
{ ... } создаёт объект. user получает ссылку на него.Имён может быть сколько угодно, а объект по-прежнему один:
const user = { name: 'Ира' };const admin = user;const owner = user;owner.name = 'Лена';console.log(user.name);console.log(admin.name);Возьмём let, чтобы переменную можно было переназначить. Сначала обе переменные ведут к одному объекту, и изменение свойства через admin меняет общий объект. Но совсем другое происходит, когда переменной admin присваивают новый литерал:
let user = { name: 'Ира',};let admin = user;// меняется общий объектadmin.name = 'Макс';// новый объект: admin теперь указывает на негоadmin = { name: 'Лена',};console.log(user.name);console.log(admin.name);Литерал { name: 'Лена' } создаёт новый объект, а admin начинает указывать уже на него. Старый объект никуда не переместился – изменилась только связь между переменной admin и объектом:
Отсюда важное различие: admin. меняет объект, на который указывает admin, а admin = { name: 'Макс' } меняет значение самой переменной admin – теперь она указывает на другой объект.
const a = { name: 'Ира' };const b = { name: 'Ира' };Каждое выполнение литерала { ... } создаёт отдельный объект. Хотя содержимое выглядит одинаково, объектов здесь два. Одинаковые свойства ещё не делают их одним объектом – и именно это важно при сравнении.
Для чисел и строк === отвечает на привычный вопрос: одинаковы ли значения.
console.log(5 === 5);console.log('Ира' === 'Ира');С объектами вопрос другой:
const a = { name: 'Ира' };const b = { name: 'Ира' };console.log(a === b);Почему false, хотя объекты выглядят одинаково? Потому что литерал { ... } выполнился дважды и создал два разных объекта. При сравнении объектов === проверяет: это один и тот же объект или два разных? Теперь другая ситуация:
const a = { name: 'Ира' };const c = a;console.log(a === c);Здесь обе переменные ведут к одному объекту, поэтому результат true:
=== не сравнивает свойства объекта
Выражение { name: 'Ира' } === { name: 'Ира' } даёт false не потому, что JavaScript «не заметил» одинаковые свойства. Просто === вообще не занимается таким сравнением. Для объектов он отвечает на другой вопрос: слева и справа тот же самый объект? Если нужно узнать, одинаковы ли данные внутри двух разных объектов, свойства сравнивают отдельно:
const a = { name: 'Ира' };const b = { name: 'Ира' };console.log(a.name === b.name);Готового оператора, который сравнил бы все свойства двух объектов разом, в языке нет.
Теперь можно вернуться к примеру из начала урока. После let b = a у нас два независимых значения: 5 в a и ещё одно 5 в b. Если поменять b, a не изменится. После const admin = user второй объект не создаётся: обе переменные ведут к одному.
let a = 5;let b = a;// два значения: 5 и 5const user = { name: 'Ира' };const admin = user;// один объект, две ссылкиПри обычном присваивании примитивов вроде числа или строки копируется само значение. При присваивании объекта две переменные получают ссылки на один и тот же объект. Поэтому изменение объекта через одну переменную становится видно через другую.
Почему язык устроен именно так
Число, строка или булево значение – маленькие значения фиксированного размера, и меняться после создания они не могут. Скопировать такое значение при присваивании дёшево, а копия ничем не отличается от оригинала – поэтому язык просто копирует его.
Объект – другое дело. Он может быть большим: десятки свойств, вложенные объекты, а внутри них ещё объекты. И он меняется по ходу работы программы. Если бы каждое присваивание создавало полную копию, программа тратила бы память и время на копирование при каждом =, а разные части кода не видели бы изменений друг друга: одна часть поменяла бы свою копию, другая продолжала бы работать со своей.
Поэтому объект хранится один раз, а переменные держат ссылки на него. Копия ссылки стоит одинаково мало для объекта любого размера, и все, у кого есть ссылка, работают с одними и теми же данными. Когда независимая копия объекта действительно нужна, её создают отдельно и явно.
// теперь вы знаете
- При
const admin = userновый объект не создаётся: обе переменные указывают на один объект - Изменение свойства через одну ссылку видно через остальные ссылки на тот же объект
admin.изменяет объект, аname = … admin = { … }перенаправляет переменную на другой объект- Каждый новый литерал
{ … }создаёт новый объект - Для объектов
===проверяет, один ли это объект: одинаковые свойства сами по себе равенства не дают
Что выведет программа?
const player = {score: 10,};const copy = player;copy.score = 20;console.log(player.score);
Что появится в консоли?
let cart = { items: 1 };const saved = cart;cart = { items: 1 };console.log(cart === saved);saved.items = 2;console.log(cart.items);
Сколько объектов создаётся при выполнении этого кода?
const start = { count: 0 };let last = start;for (let i = 1; i <= 3; i++) {last = { count: i };}const copy = last;
В объекте catalog тысяча свойств. Верно ли, что const copy = catalog; выполняется дольше, чем такое же присваивание объекта с одним свойством?
Что выведут два сравнения?
const player = { score: 10 };const copy = player;const other = { score: 10 };console.log(player === copy);console.log(player === other);