Menu
Русский

Optional Chaining (?.) в JavaScript: безопасный доступ

Как оператор ?. помогает добираться до вложенных свойств, элементов массива и методов, не падая на null и undefined.

Проблема: обращение к вложенным свойствам — дело ненадёжное

Лезть вглубь вложенного объекта — вполне нормально. Ровно до того момента, пока один из уровней вдруг не окажется отсутствующим:

index.js
Output
Click Run to see the output here.

user.address — это undefined, а попытка прочитать .city у undefined выбрасывает TypeError: Cannot read properties of undefined. Реальные данные — ответы API, распарсенный JSON, результаты запросов к DOM — сплошь состоят из полей, которых может и не быть. А писать защитные проверки на каждом уровне быстро превращается в шум:

const city = user && user.address && user.address.city;

Читается нормально при двух уровнях вложенности. А вот на четырёх — уже боль. Оператор ?. решает эту проблему куда элегантнее.

Как работает ?.: короткое замыкание на null и undefined

Optional chaining в JavaScript читает свойство только в том случае, если значение перед точкой — не null и не undefined. Если же там одно из этих значений, всё выражение сразу возвращает undefined и дальше не выполняется.

index.js
Output
Click Run to see the output here.

В первой строке name читается как обычно. Во второй строке мы обращаемся к user.address, получаем undefined — и дальше цепочка обрывается. Третья строка без ?. выбросила бы ошибку, потому что .toUpperCase() вызвался бы у undefined; а с ?. всё выражение спокойно возвращает undefined.

Запомнить это просто: ?. говорит «если то, что стоит передо мной, равно null или undefined — сдавайся и верни undefined, иначе продолжай дальше».

Optional chaining для массивов и вызова функций

Три варианта синтаксиса, суть одна:

index.js
Output
Click Run to see the output here.
  • ?.[...] — доступ к элементу массива или динамическому свойству.
  • ?.() — вызов того, что может и не оказаться функцией.
  • ?.name — обычное обращение к свойству.

Все три варианта одинаково срабатывают по принципу короткого замыкания. user?.notAMethod?.() не выбросит ошибку, даже если notAMethod не существует — второй ?. увидит undefined и просто остановится.

Особенно удобно это с необязательными колбэками:

index.js
Output
Click Run to see the output here.

Больше никаких охранников в духе if (typeof onDone === "function") перед каждым вызовом.

Короткое замыкание срабатывает только на null и undefined

Вот тут новички часто спотыкаются. Оператор ?. реагирует исключительно на nullish-значения. Остальные falsy-значения — 0, "", false, NaN — это вполне валидные объекты для продолжения цепочки (ну, насколько это позволяет автобоксинг):

index.js
Output
Click Run to see the output here.

data.count равно 0 — значение ложное (falsy), но не является null или undefined, поэтому ?.toFixed(2) спокойно отрабатывает и возвращает "0.00". Сравните с тем, как ведёт себя &&:

index.js
Output
Click Run to see the output here.

Версия с && возвращает 0, потому что data.count — falsy-значение, и выражение закорачивается. Версия с ?. возвращает "0.00", потому что 0 не является nullish. Если вам действительно нужно остановиться на 0, берите &&. Если же останавливаться нужно только когда «значения нет», ваш выбор — ?..

Где ставить ? — это важно

Оператор ?. защищает то, что стоит перед ним, а не после. Поэтому ставьте его на том уровне, который может отсутствовать:

index.js
Output
Click Run to see the output here.

Все три варианта работают, потому что на деле ничего не отсутствует. Но если config.server может оказаться undefined, нужно писать config.server?.host — ставить ?. перед server бесполезно, ведь проблема именно в попытке достучаться до .host у несуществующего server.

Хорошее правило: ставьте ?. на тех уровнях, где значение перед оператором действительно может быть null или undefined. Если лепить ?. после каждой точки «на всякий случай», это маскирует настоящие баги и превращает код в кашу.

Присваивание через ?. не работает

Optional chaining работает только на чтение. Слева от оператора присваивания его использовать нельзя:

user?.address?.city = "Paris";   // SyntaxError

Оно и логично, если вдуматься — что вообще должно означать присваивание свойству undefined? Если нужно записать значение только при условии, что родительский объект существует, распишите это явно:

index.js
Output
Click Run to see the output here.

Когда стоит использовать ?., а когда — нет

Оператор ?. раскрывается во всей красе, когда значение действительно может отсутствовать:

  • Ответы API, где поля могут быть, а могут и не быть.
  • Поиск элементов в DOM: document.querySelector(".banner")?.remove().
  • Колбэки, которые могут и не прийти: options.onError?.(err).
  • Цепочки вызовов библиотек, где промежуточный результат может оказаться null.

А вот когда значение обязано быть на месте — это уже не тот инструмент. Если расставлять ?. просто чтобы заглушить ошибки, шумный и легко находимый баг («TypeError в строке 42») превращается в тихий: переменная загадочным образом оказывается undefined где-то через три функции. Если что-то должно существовать — пусть код падает. Стек-трейс в этом случае вам только на руку.

Пример из жизни

Достаём значение из ответа API, который может прийти неполным:

index.js
Output
Click Run to see the output here.

Каждый ?. отвечает за один уровень неопределённости. avatarUrl аккуратно получит undefined, а onClick?.() вызовет обработчик, только если он действительно есть.

Обратите внимание на ?? в строке с displayName — это вторая половина того же паттерна. ?. отдаёт undefined, когда чего-то нет; ?? позволяет подставить значение по умолчанию, не спотыкаясь о вполне валидные «ложные» значения вроде 0 или "".

Дальше: оператор нулевого слияния

?. и ?? — это одна и та же идея, применённая к разным задачам: оба считают «отсутствующими» только null и undefined, а остальные falsy-значения не трогают. Дальше разберём, как ?? помогает задавать адекватные значения по умолчанию — и почему || все эти годы тихо делал это неправильно.

Часто задаваемые вопросы

Что делает оператор ?. в JavaScript?

?. обращается к свойству, индексу массива или методу только если значение слева от него не null и не undefined. Если там как раз null или undefined, выражение замыкается и возвращает undefined вместо того, чтобы бросить ошибку. Например, user?.address?.city спокойно отработает, даже если user или address отсутствует.

Когда стоит использовать optional chaining?

Тогда, когда значение действительно может отсутствовать по смыслу: ответы API с необязательными полями, поиск DOM-элемента, которого может не оказаться, опциональные коллбэки. А вот маскировать ?. баги, где значение обязано быть, — плохая идея. В таких местах отсутствующее значение — это полезный сигнал, и лучше увидеть ошибку, чем молчаливый undefined.

В чём разница между ?. и &&?

В большинстве случаев результат одинаковый, но есть важный нюанс: ?. срабатывает только на null и undefined, а && — на любое falsy-значение, включая 0, '' и false. То есть obj && obj.count вернёт 0, если count равен 0, — и obj?.count тоже вернёт 0, но цепочка прерывается именно на null/undefined, а не на любом «пустом» значении.

Можно ли использовать ?. с массивами и вызовами функций?

Да. arr?.[0] безопасно читает элемент массива, а fn?.() вызывает функцию только если она существует. Правило то же: если слева от ?. оказался null или undefined, всё выражение даёт undefined, и дальше ничего не выполняется.

Учитесь программировать с Coddy

НАЧАТЬ