Опциональная цепочка ?.
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Данные из реального мира редко бывают полными: у одного пользователя есть адрес, у другого его никто не заполнил. Оператор ?. — это точка, которая умеет вовремя остановиться: вместо падения программы она честно возвращает undefined.
Читать свойство у объекта — привычная операция: user.name. Но если по пути встретится значение, которого нет, точка перестаёт быть безобидной. Обращение к свойству undefined или null — это не «пусто», а ошибка выполнения, после которой скрипт останавливается.
Обратите внимание на разницу: user.address спокойно вернул undefined — одно отсутствующее свойство читать можно. А вот undefined.city — уже TypeError. Мы печатаем e.constructor.name, а не текст ошибки: формулировка отличается от браузера к браузеру, а имя типа везде одинаковое.
До 2020 года это лечили длинными проверками через &&: пока каждое звено «правдиво», идём дальше. Работает, но имя объекта приходится повторять столько раз, сколько в цепочке точек.
Опциональная цепочка ?. заменяет всю эту конструкцию. Правило одно: если значение слева от ?. равно null или undefined, выражение немедленно возвращает undefined. Во всех остальных случаях ?. ведёт себя как обычная точка и не добавляет никакой магии.
Важно, что проверяются только эти два значения. Ноль, пустая строка, false и NaN для ?. — совершенно нормальные значения, цепочка на них не обрывается. Этим ?. отличается от старой проверки через &&, которая спотыкалась на любом «ложном» звене.
Читайте ?. вслух как «если есть — иди дальше, если нет — верни undefined и молчи».
?. спасает от отсутствующего значения, но не от отсутствующей переменной. Если имени нет в области видимости, получите ReferenceError ещё до того, как оператор вступит в дело.
?. не просто подставляет undefined вместо результата — он обрывает вычисление. Как только слева обнаружились null или undefined, вся правая часть выражения пропускается: не читаются свойства, не вычисляются ключи, не вызываются функции. Это и называется коротким замыканием.
Замыкание останавливает только текущую цепочку, а не всё выражение целиком. В строке user?.name + "!" сложение выполнится в любом случае: сначала цепочка даст undefined, а потом к нему приклеится восклицательный знак. Скобки задают границу: (user?.name).length уже упадёт с ошибкой.
Свойства — не единственное место, где может не оказаться значения. Метода может не быть у объекта, а ключ может приходить переменной. Для этого есть ещё две формы: ?.() перед вызовом и ?.[] перед чтением по вычисляемому ключу.
?.() проверяет саму функцию: если guest.say не существует, вызова не будет и мы получим undefined вместо «say is not a function». Форма ?.[] устроена так же, только вместо вызова — доступ по ключу в квадратных скобках.
| Запись | Что проверяется | Результат при null/undefined |
|---|---|---|
| obj?.prop | сам obj перед чтением свойства | undefined |
| obj?.[key] | сам obj перед чтением по ключу | undefined |
| fn?.() | сама fn перед вызовом | undefined |
?. делает код короче, но не делает его умнее. Он проверяет ровно одно значение — то, что стоит слева от него, — и ничего не знает о смысле ваших данных. Самая частая ошибка новичка: поставить ?. в начале цепочки и решить, что дальше можно расслабиться.
Второй подвох — избыточность. Если объект обязан существовать по логике программы, писать ?. вредно: настоящая ошибка (сломанный ответ сервера, забытая инициализация) превратится в тихий undefined и всплывёт где-нибудь через десять строк. Ставьте ?. там, где отсутствие значения — нормальная ситуация, а не там, где просто страшно.
И наконец, ?. — оператор чтения. Слева от знака = он не работает: user?.name = "Боря" — синтаксическая ошибка. Зато он отлично дружит с ??, когда нужно подставить значение по умолчанию.
Раньше опечатка в имени свойства падала с громкой ошибкой на следующей же точке. С ?. она молча превращается в undefined — код «работает», просто ничего не показывает. Это ещё один довод не ставить оператор по всей цепочке подряд.
obj?.propвозвращаетundefined, еслиobjравенnullилиundefined, и работает как обычная точка во всех остальных случаях.- Проверяются только
nullиundefined: ноль, пустая строка иfalseцепочку не обрывают. - Короткое замыкание пропускает весь остаток цепочки — ключи не вычисляются, функции не вызываются.
- Есть три формы:
?.для свойств,?.[]для вычисляемых ключей,?.()для вызова метода. ?.защищает только то значение, что стоит слева; он не заменяет проверку логики и не годится для записи.
1/3Объект user = { name: "Аня" }. Что выведет console.log(user.address?.city)?
2/3let n = 0; const key = () => { n += 1; return "a"; }; После null?.[key()] чему равно n?
3/3У объекта user нет свойства address. Что сделает user?.address.city?
Достать город из неполных данных
Профили пользователей приходят с сервера в разной полноте: у кого-то заполнен адрес, у кого-то только имя, а иногда вместо объекта приходит null. Напишите функцию getCity(user), которая возвращает user.profile.address.city, а если по пути чего-то не хватает — строку "неизвестно". Ни одна проверка не должна падать с ошибкой.
Цепочку ?. ставим на каждом звене, которого может не быть, а подстановку значения по умолчанию отдаём оператору ?? — он сработает ровно на undefined и null.
Использовать здесь || вместо ?? было бы рискованно: пустая строка в поле города тоже заменилась бы на «неизвестно», хотя формально значение пришло.
Необязательный метод
Напишите функцию tryLog(obj, msg): если у объекта есть метод write, она вызывает его с сообщением и возвращает true; если метода или самого объекта нет — просто возвращает false, ничего не вызывая и не бросая ошибок.
Цепочка obj?.write достаёт свойство, не падая ни на отсутствующем объекте, ни на отсутствующем методе, а typeof решает, можно ли его звать.
Порядок здесь важен. Написать сначала obj?.write?.(msg), а проверку typeof оставить на возврат, нельзя: ?.() страхует только от null и undefined, и если в write лежит строка, вызов упадёт с TypeError — ровно та ошибка, которой задача запрещает случиться.