Опциональная цепочка в JavaScript (?.)
Оператор ?. в JavaScript: безопасный доступ к вложенным свойствам object, вызов методов и чтение динамических ключей без ошибок.
Оператор опциональной цепочки ?. — это короткий и безопасный способ читать значение глубоко внутри цепочки связанных объектов, когда одно из звеньев этой цепочки может отсутствовать. Вместо того чтобы выбрасывать ошибку, он просто останавливается и возвращает undefined. В этой главе рассматривается проблема, которую он решает, три формы его записи и несколько правил, необходимых для его правильного использования.
Проблема: обращение к отсутствующим свойствам
Представьте, что у вас есть object user, и вы хотите прочитать город из вложенного object address. Если address отсутствует, обращение к его свойству выбрасывает ошибку:
let user = {}; // a user without an address
console.log(user.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')Причина в том, что user.address равно undefined, а читать .city из undefined нельзя. Это распространённая ситуация, когда данные поступают из API, формы или базы данных, где некоторые поля необязательны.
До появления ?. разработчики обходили это с помощью цепочек && или вложенных проверок. Они работают, но они многословны и повторяют имена свойств:
let user = {};
// The old way with the && operator
let city = user && user.address && user.address.city;
console.log(city); // undefined
// Or with an even longer ternary
let city2 = user ? (user.address ? user.address.city : undefined) : undefined;
console.log(city2); // undefinedКак работает опциональная цепочка
Оператор опциональной цепочки ?. останавливает вычисление, если значение перед ним равно null или undefined, и возвращает undefined вместо выброса ошибки. Такое поведение называется коротким замыканием (short-circuiting).
Вот тот же запрос, теперь безопасный и читаемый:
Когда user.address равно undefined, выражение user?.address?.city тихо возвращает undefined. Когда данные присутствуют, цепочка читает всё до конца, вплоть до city.
?. считает «отсутствующими» только null и undefined. Другие ложные значения, такие как 0, '' (пустая string) или false, являются настоящими значениями, поэтому цепочка продолжает работать через них в обычном режиме.
Он защищает только значение слева
Это самое важное правило, и новички чаще всего ошибаются именно здесь. ?. проверяет только значение непосредственно слева от него, а не всю цепочку.
Рассмотрим a?.b.c:
- Если
aравноnullилиundefined, всё выражение прерывается по короткому замыканию и возвращаетundefined. - Но если
aсуществует, аbравноnullилиundefined, то.cпо-прежнему является обычным обращением — и оно выбросит ошибку.
let a = { b: null };
console.log(a?.b); // null — fine, a exists
console.log(a?.b.c); // TypeError: Cannot read properties of null (reading 'c')Исправление: поставьте ?. в каждом месте, где значение может законно отсутствовать:
let a = { b: null };
console.log(a?.b?.c); // undefined — no errorПравило простое: ставьте ?. непосредственно после каждого значения, которого может не быть, а не только в начале цепочки.
Короткое замыкание останавливает всю цепочку
Когда часть перед ?. равна null или undefined, остаток выражения вообще не вычисляется. Это включает любые вызовы функций и любые последующие обращения к свойствам, стоящие после него.
Это полезная гарантия: ничто после отсутствующего звена не получит шанса выполниться или вызвать побочные эффекты.
Три формы опциональной цепочки
?. — это не самостоятельный оператор, а синтаксическая конструкция, которая существует в трёх формах — по одной для каждого способа обращения к значению.
?. для доступа к свойствам
Используйте obj?.prop для чтения свойства, когда obj может быть null или undefined.
let user = { name: 'John' };
console.log(user?.name); // John
console.log(user?.surname); // undefined (key missing, but no error)?.() для вызова методов
Используйте obj.method?.() для вызова метода только в том случае, если он существует. Это удобно, когда object может поддерживать или не поддерживать данный метод.
Если greet не является функцией (потому что она отсутствует), ?.() пропускает вызов и возвращает undefined вместо того, чтобы выбрасывать TypeError: greet is not a function.
?.[] для динамических ключей
Используйте obj?.[key], когда вы читаете свойство с помощью скобочной нотации — например, динамического ключа, хранящегося в переменной.
let user = { name: 'Alice' };
let key = 'name';
console.log(user?.[key]); // Alice
let nobody = null;
console.log(nobody?.[key]); // undefined (no error)Опциональная цепочка предназначена для чтения, а не для записи
Вы можете использовать ?. для чтения значения и даже для безопасного удаления свойства с помощью delete. Но вы не можете использовать его в левой части присваивания.
let user = null;
// Reading is fine
console.log(user?.name); // undefined
// Deleting is fine — delete is skipped if user is null
delete user?.name; // no error
// Assignment does NOT work — this is a syntax error
// user?.name = 'John'; // SyntaxErrorПричина в том, что присваивать нечему, когда левая часть отсутствует, поэтому язык это не допускает.
Не злоупотребляйте опциональной цепочкой
?. — это точный инструмент: используйте его только там, где значение слева действительно необязательно. Применять его везде подряд — ошибка, потому что это может скрывать настоящие баги.
Например, если вы опечатаетесь в имени свойства, ?. молча вернёт undefined вместо того, чтобы дать вам заметить проблему:
let user = { name: 'John' };
console.log(user?.nmae); // undefined — typo hidden, no error to alert youЕсли переменная вроде user должна всегда существовать (например, это обязательный аргумент), обращайтесь к ней напрямую: user.address?.city, а не user?.address?.city. Тогда действительно отсутствующий user всё равно выбросит ошибку как можно раньше, что поможет вам поймать баг, а не замаскировать его.
Сочетание с оператором нулевого слияния
Опциональная цепочка возвращает undefined, когда звено отсутствует, и это прекрасно сочетается с оператором нулевого слияния ?? для подстановки значения по умолчанию.
Здесь user?.name вычисляется в undefined, после чего ?? вступает в игру и подставляет запасное значение 'Anonymous'. Такое сочетание — один из самых распространённых практических случаев применения ?..
Поддержка браузерами
Опциональная цепочка является частью спецификации ES2020. Она поддерживается во всех современных браузерах (Chrome, Firefox, Safari и Edge) и в Node.js 14 и выше, поэтому вы можете использовать её без транспилятора в актуальных средах. Чтобы глубже разобраться в значениях, которые она проверяет, смотрите типы данных JavaScript и методы object и this.