Опциональная цепочка ?.
— это безопасный способ доступа к свойствам вложенных объектов, даже если какое-либо из промежуточных свойств не существует.
Проблема «несуществующего свойства»
Если вы только начали читать учебник и изучать JavaScript, то, возможно, проблема вас ещё не коснулась, но она довольно распространена.
В качестве примера предположим, что у нас есть объекты user
, которые содержат информацию о наших пользователях.
У большинства наших пользователей есть адреса в свойстве user.address
с улицей user.address.street
, но некоторые из них их не указали.
В таком случае, когда мы попытаемся получить user.address.street
, а пользователь окажется без адреса, мы получим ошибку:
let user = {}; // пользователь без свойства "address"
alert(user.address.street); // Ошибка!
Это ожидаемый результат. JavaScript работает следующим образом. Поскольку user.address
имеет значение undefined
, попытка получить user.address.street
завершается ошибкой.
Во многих практических случаях мы бы предпочли получить здесь undefined
вместо ошибки (что означало бы «улицы нет»).
…Или ещё один пример. В веб-разработке мы можем получить объект, соответствующий элементу веб-страницы, с помощью специального вызова метода, такого как document.querySelector('.elem')
, и он возвращает null
, когда такого элемента нет.
// document.querySelector('.elem') равен null, если элемента нет
let html = document.querySelector('.elem').innerHTML; // ошибка, если он равен null
Ещё раз, если элемент не существует, мы получим сообщение об ошибке доступа к свойству .innerHTML
у null
. И в некоторых случаях, когда отсутствие элемента является нормальным, мы хотели бы избежать ошибки и просто принять html = null
в качестве результата.
Как мы можем это сделать?
Очевидным решением было бы проверить значение с помощью if
или условного оператора ?
, прежде чем обращаться к его свойству, вот так:
let user = {};
alert(user.address ? user.address.street : undefined);
Это работает, тут нет ошибки… Но это довольно неэлегантно. Как вы можете видеть, "user.address"
появляется в коде дважды.
Вот как то же самое выглядело бы для document.querySelector
:
let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;
Как видно, поиск элемента document.querySelector('.elem')
здесь вызывается дважды, что не очень хорошо.
Для более глубоко вложенных свойств это ещё менее красиво, поскольку потребуется больше повторений.
К примеру, давайте аналогично вычислим user.address.street.name
.
Нам нужно проверить как user.address
, так и user.address.street
:
let user = {}; // у пользователя нет адреса
alert(user.address ? user.address.street ? user.address.street.name : null : null);
Это просто ужасно, у кого-то могут даже возникнуть проблемы с пониманием такого кода.
Есть немного лучший способ написать это, используя оператор &&
:
let user = {}; // пользователь без адреса
alert( user.address && user.address.street && user.address.street.name ); // undefined (без ошибки)
Проход при помощи логического оператора И &&
через весь путь к свойству гарантирует, что все компоненты существуют (если нет, вычисление прекращается), но также не является идеальным.
Как вы можете видеть, имена свойств по-прежнему дублируются в коде. Например, в приведённом выше коде user.address
появляется три раза.
Вот почему в язык была добавлена опциональная цепочка ?.
. Чтобы решить эту проблему – раз и навсегда!
Опциональная цепочка
Опциональная цепочка ?.
останавливает вычисление и возвращает undefined
, если значение перед ?.
равно undefined
или null
.
Далее в этой статье, для краткости, мы будем говорить, что что-то «существует», если оно не является null
и не undefined
.
Другими словами, value?.prop
:
- работает как
value.prop
, если значениеvalue
существует, - в противном случае (когда
value
равноundefined/null
) он возвращаетundefined
.
Вот безопасный способ получить доступ к user.address.street
, используя ?.
:
let user = {}; // пользователь без адреса
alert( user?.address?.street ); // undefined (без ошибки)
Код лаконичный и понятный, в нем вообще нет дублирования.
А вот пример с document.querySelector
:
let html = document.querySelector('.elem')?.innerHTML; // будет undefined, если элемента нет
Считывание адреса с помощью user?.address
работает, даже если объект user
не существует:
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
Обратите внимание: синтаксис ?.
делает необязательным значение перед ним, но не какое-либо последующее.
Так например, в записи user?.address.street.name
?.
позволяет user
безопасно быть null/undefined
(и в этом случае возвращает undefined
), но это так только для user
. Доступ к последующим свойствам осуществляется обычным способом. Если мы хотим, чтобы некоторые из них были необязательными, тогда нам нужно будет заменить больше .
на ?.
.
Нам следует использовать ?.
только там, где нормально, что чего-то не существует.
К примеру, если, в соответствии с логикой нашего кода, объект user
должен существовать, но address
является необязательным, то нам следует писать user.address?.street
, но не user?.address?.street
.
В этом случае, если вдруг user
окажется undefined
, мы увидим программную ошибку по этому поводу и исправим её. В противном случае, если слишком часто использовать ?.
, ошибки могут замалчиваться там, где это неуместно, и их будет сложнее отлаживать.
?.
должна быть объявленаЕсли переменной user
вообще нет, то user?.anything
приведёт к ошибке:
// ReferenceError: user is not defined
user?.address;
Переменная должна быть объявлена (к примеру, как let/const/var user
или как параметр функции). Опциональная цепочка работает только с объявленными переменными.
Сокращённое вычисление
Как было сказано ранее, ?.
немедленно останавливает вычисление, если левая часть не существует.
Так что если после ?.
есть какие-то вызовы функций или операции, то они не произойдут.
Например:
let user = null;
let x = 0;
user?.sayHi(x++); // нет "user", поэтому выполнение не достигает вызова sayHi и x++
alert(x); // 0, значение не увеличилось
Другие варианты применения: ?.(), ?.[]
Опциональная цепочка ?.
— это не оператор, а специальная синтаксическая конструкция, которая также работает с функциями и квадратными скобками.
Например, ?.()
используется для вызова функции, которая может не существовать.
В приведённом ниже коде у некоторых наших пользователей есть метод admin
, а у некоторых его нет:
let userAdmin = {
admin() {
alert("Я админ");
}
};
let userGuest = {};
userAdmin.admin?.(); // Я админ
userGuest.admin?.(); // ничего не произойдет (такого метода нет)
Здесь в обеих строках мы сначала используем точку (userAdmin.admin
), чтобы получить свойство admin
, потому что мы предполагаем, что объект userAdmin
существует, так что читать из него безопасно.
Затем ?.()
проверяет левую часть: если функция admin
существует, то она запускается (это так для userAdmin
). В противном случае (для userGuest
) вычисление остановится без ошибок.
Синтаксис ?.[]
также работает, если мы хотим использовать скобки []
для доступа к свойствам вместо точки .
. Как и в предыдущих случаях, он позволяет безопасно считывать свойство из объекта, который может не существовать.
let key = "firstName";
let user1 = {
firstName: "John"
};
let user2 = null;
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
Также мы можем использовать ?.
с delete
:
delete user?.name; // удаляет user.name если пользователь существует
?.
для безопасного чтения и удаления, но не для записиОпциональная цепочка ?.
не имеет смысла в левой части присваивания.
Например:
let user = null;
user?.name = "John"; // Ошибка, не работает
// то же самое что написать undefined = "John"
Итого
Синтаксис опциональной цепочки ?.
имеет три формы:
obj?.prop
– возвращаетobj.prop
еслиobj
существует, в противном случаеundefined
.obj?.[prop]
– возвращаетobj[prop]
еслиobj
существует, в противном случаеundefined
.obj.method?.()
– вызываетobj.method()
, еслиobj.method
существует, в противном случае возвращаетundefined
.
Как мы видим, все они просты и понятны в использовании. ?.
проверяет левую часть на null/undefined
и позволяет продолжить вычисление, если это не так.
Цепочка ?.
позволяет безопасно получать доступ к вложенным свойствам.
Тем не менее, мы должны использовать ?.
осторожно, только там, где по логике кода допустимо, что левая часть не существует. Чтобы он не скрывал от нас ошибки программирования, если они возникнут.