Опциональная цепочка
Длинные цепочки доступа к свойствам в JavaScript могут быть рискованными, так как любое из них может оцениваться как null
или undefined
(также известные как "значения nullish"). Проверка существования свойства на каждом шаге легко превращается в глубокую вложенную структуру if
-условий или длинное if
-условие, воспроизводящее цепочку доступа к свойствам:
// Ошибочный вариант, может выдать ошибку.
const nameLength = db.user.name.length;
// Менее ошибочный, но сложнее читать.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;
Вышеописанное также можно выразить с помощью тернарного оператора, что не особо помогает читабельности:
const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);
Представление оператора опциональной цепочки
Конечно, никто не хочет писать код таким образом, поэтому альтернатива будет желательна. Некоторые другие языки предлагают изящное решение этой проблемы с помощью функции, называемой "опциональная цепочка". Согласно недавнему предложению спецификации, "опциональная цепочка — это цепочка из одного или более доступов к свойствам и вызовов функций, первая из которых начинается с токена ?.
".
Используя новый оператор опциональной цепочки, мы можем переписать приведенный выше пример следующим образом:
// Всё ещё проверяет ошибки и гораздо более читабельно.
const nameLength = db?.user?.name?.length;
Что происходит, если db
, user
или name
равны undefined
или null
? С оператором опциональной цепочки JavaScript инициализирует nameLength
как undefined
вместо выброса ошибки.
Обратите внимание, что такое поведение также более надёжно, чем наша проверка if (db && db.user && db.user.name)
. Например, что если name
всегда гарантировано быть строкой? Мы могли бы изменить name?.length
на name.length
. Тогда, если name
была пустой строкой, мы всё ещё получили бы правильную длину 0
. Это потому, что пустая строка является ложным значением: она ведет себя как false
в условии if
. Оператор опциональной цепочки устраняет этот частый источник ошибок.
Дополнительные формы синтаксиса: вызовы и динамические свойства
Существует также версия оператора для вызова опциональных методов:
// Расширяет интерфейс с опциональным методом, который присутствует
// только для пользователей-администраторов.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;
Синтаксис может показаться неожиданным, так как ?.()
— это фактический оператор, который применяется к выражению перед ним.
Существует третье использование оператора, а именно опциональный доступ к динамическим свойствам, который осуществляется через ?.[]
. Он либо возвращает значение, указанное аргументом в скобках, либо undefined
, если объект отсутствует для получения значения. Вот возможный случай использования, следуя примеру выше:
// Расширяет возможности статического доступа к свойствам
// с динамически генерируемым именем свойства.
const optionName = 'опциональная настройка';
const optionLength = db?.user?.preferences?.[optionName].length;
Эта последняя форма также доступна для опционального индексирования массивов, например:
// Если `usersArray` равен `null` или `undefined`,
// тогда `userName` корректно оценивается как `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;
Оператор опциональной цепочки можно комбинировать с оператором слияния nullish ??
, когда требуется значение по умолчанию, отличное от undefined
. Это позволяет безопасно осуществлять доступ к свойствам в глубине с указанным значением по умолчанию, решая общий случай использования, который ранее требовал сторонних библиотек, таких как _.get
lodash:
const object = { id: 123, names: { first: 'Алиса', last: 'Смит' }};
{ // С lodash:
const firstName = _.get(object, 'names.first');
// → 'Алиса'
const middleName = _.get(object, 'names.middle', '(нет второго имени)');
// → '(нет второго имени)'
}
{ // С опциональной цепочкой и слиянием nullish:
const firstName = object?.names?.first ?? '(нет первого имени)';
// → 'Алиса'
const middleName = object?.names?.middle ?? '(нет второго имени)';
// → '(нет второго имени)'
}
Свойства оператора опциональной цепочки
У оператора опциональной цепочки есть несколько интересных свойств: короткое замыкание, накопление и опциональное удаление. Давайте рассмотрим каждое из них на примере.
Короткое замыкание означает не выполнение остальной части выражения, если оператор опциональной цепочки возвращает значение раньше:
// `age` увеличивается только если `db` и `user` определены.
db?.user?.grow(++age);
Накопление означает, что более одного оператора опциональной цепочки может быть применено в последовательности обращения к свойствам:
// Опциональная цепочка может следовать за другой опциональной цепочкой.
const firstNameLength = db.users?.[42]?.names.first.length;
Тем не менее, будьте осторожны, используя более одного оператора опциональной цепочки в одной цепочке. Если значение гарантированно не является nullish, использование ?.
для доступа к его свойствам нежелательно. В приведенном выше примере предполагается, что db
всегда определен, но db.users
и db.users[42]
могут быть не определены. Если такой пользователь есть в базе данных, то names.first.length
считается всегда определенным.
Опциональное удаление означает, что оператор delete
может быть комбинирован с опциональной цепочкой:
// `db.user` удаляется только если `db` определен.
delete db?.user;
Больше подробностей можно найти в разделе Semantics предложения.