Вложенные объекты и безопасный доступ
В прошлом уроке объект user хранил простые значения: имя, возраст, город. Но данные часто сами имеют внутреннюю структуру. Например, адрес – это не одно значение: у него есть город, дом, а позже могут появиться улица, квартира и индекс.
Вместо десятка отдельных свойств удобнее собрать адрес в собственный объект и положить его внутрь user:
const user = { name: 'Ира', address: { city: 'Москва', house: 15, },};console.log(user.address.city);console.log(user.address.house);Такой объект называют вложенным. Здесь значением свойства address является ещё один объект.
Цепочка точек – несколько обращений подряд
JavaScript не прыгает сразу к city. Он выполняет цепочку по одному шагу слева направо:
- берёт значение
user; - читает у него
address; - получает объект
{ city: 'Москва', house: 15 }; - уже у этого объекта читает
city.
user. – просто короткая запись этой последовательности обращений.
user читаем свойство address.Читать и изменять вложенные свойства можно той же цепочкой:
const user = { name: 'Ира', address: { city: 'Москва', house: 15 },};user.address.city = 'Казань';console.log(user.address.city);user.address = { city: 'Тверь', house: 3 };console.log(user.address.house);В первом присваивании меняется только свойство city внутри объекта address. Во втором присваивание стоит уже после user., поэтому старый объект address целиком заменяется новым:
user.address.city = 'Казань';// address: { city: 'Казань', house: 15 }user.address = { city: 'Тверь', house: 3 };// address: { city: 'Тверь', house: 3 }Не у каждого пользователя указан адрес:
const guest = { name: 'Гость',};console.log(guest.address);console.log(guest.address.city);Первое обращение совершенно допустимо: guest. → undefined. Отсутствующее свойство объекта само по себе ошибки не вызывает.
Проблема начинается на следующем шаге. После первого обращения выражение по смыслу превращается в undefined.. А прочитать свойство у undefined нельзя – поэтому JavaScript выбрасывает TypeError:
Сообщение Cannot read properties of undefined (reading 'city') буквально подсказывает причину: JavaScript попытался прочитать city у значения undefined. Поэтому искать проблему нужно в предыдущем звене цепочки: почему guest. оказался undefined.
Проверка перед обращением
const guest = { name: 'Гость',};if (guest.address) { console.log(guest.address.city);} else { console.log('Адрес не указан');}Проверка решает проблему: к .city мы доходим только тогда, когда address существует. Здесь это безопасно, потому что address ожидается либо объектом, либо undefined. Для одной проверки это нормально. Но если цепочка длинная, такой код быстро разрастается. Для безопасного чтения свойства в JavaScript есть короткая запись – ?..
Оператор ?. делает следующий переход по цепочке безопасным. Он проверяет значение слева:
- если там объект – свойство читается как обычно;
- если там
nullилиundefined– выражение сразу возвращаетundefined, а следующий переход не выполняется.
const guest = { name: 'Гость',};const user = { name: 'Ира', address: { city: 'Москва', },};console.log(guest.address?.city);console.log(user.address?.city);У guest свойства address нет: слева от ?. оказался undefined, переход к city не выполнился, результат – undefined. У user адрес есть, и ?. сработал как обычная точка.
Останавливают ?. только два значения – null и undefined. Ноль, пустая строка и false для него обычные значения: на них цепочка не прервётся и пойдёт дальше:
const empty = '';const address = null;let city;console.log(empty?.length);console.log(address?.city);console.log(city?.length);guest.address.?. защищает только переход сразу после себя
const order = { customer: { name: 'Ира' },};// customer есть, address нет – упадёт на .cityconsole.log(order.customer?.address.city);Запись order. проверяет только customer. Если customer отсутствует, ?. вернёт undefined. Но если customer существует, а address нет, код всё равно дойдёт до undefined. и упадёт. Поэтому ?. ставят перед каждым переходом, где промежуточного значения действительно может не быть:
console.log(order.customer?.address?.city);Если в order может лежать undefined или null, цепочка начинается так: order.
?. работает и с квадратными скобками
const user = { settings: { theme: 'dark', },};const guest = {};const key = 'theme';console.log(user.settings?.[key]);console.log(guest.settings?.[key]);Для динамического ключа пишут ?.[. Идея та же: если settings существует, JavaScript вычислит key и прочитает нужное свойство. Если settings равен null или undefined, результатом станет undefined.
Через ?. нельзя присваивать
// SyntaxError: слева от присваивания ?. нельзяuser.address?.city = 'Казань';Optional chaining нельзя использовать слева от обычного присваивания. Если свойство нужно изменить, сначала убедитесь, что промежуточный объект существует:
if (user.address) { user.address.city = 'Казань';}// теперь вы знаете
- Значение свойства может быть объектом; цепочка
user.– несколько обращений подряд, по одному на точкуaddress. city user.меняет свойство внутриaddress. city = … address,user.заменяет вложенный объект целикомaddress = { … } - Отсутствующее свойство даёт
undefined, а чтение свойства уundefined–TypeError; причину ищут в предыдущем звене ?.проверяет значение слева: объект – читаем дальше,nullилиundefined– всё выражение даётundefined?.защищает только переход сразу после себя; с динамическим ключом пишут?.[; слева от присваиванияkey] ?.нельзя?.ставят там, где отсутствие данных – нормальная ситуация, а не «на всякий случай»
Верно ли: если у объекта нет свойства address, то уже чтение user. вызывает ошибку?
Что появится в консоли?
const user = { name: 'Ира' };console.log(user.address?.city);
Последняя строка падает с ошибкой TypeError: Cannot read properties of undefined (reading 'address'). Чтение какого свойства вернуло undefined? Впишите имя свойства.
const order = {id: 7,customer: {name: 'Ира',address: { street: 'Лесная', house: 4 },},};console.log(order.delivery.address.street);
Что появится в консоли?
const user = {name: 'Ира',nickname: '',address: null,};console.log(user.nickname?.length);console.log(user.address?.city);
По правилам программы у каждого заказа обязательно есть покупатель – свойство customer. Верно ли, что здесь ?. не нужен и лучше писать order.?
const order = { id: 3, total: 1200 };console.log(order.customer?.name);
Что произойдёт при выполнении последней строки?
const order = { customer: { name: 'Ира' } };console.log(order.customer?.address.city);
Можно ли записать user., чтобы изменить город только при наличии адреса?
Что появится в консоли?
const guest = {};const key = 'theme';console.log(guest.settings?.[key]);