Опциональная цепочка в JS
Привет, друг!
Сегодня мы поговорим об опциональной цепочке ‘?.’ в JavaScript.
Ты наверняка сталкивался с ситуациями, когда твой код выводил ошибки, если обращался к свойствам или методам несуществующих объектов. Это неприятно, но мы можем упростить себе жизнь с помощью опциональной цепочки ‘?.’.
Допустим, у нас есть объект «person», который содержит информацию о человеке:
const person = name: ‘John’,
age: 25,
hobbies: [‘football’, ‘basketball’]
>;
И мы хотим вывести в консоль информацию о любимом хобби человека:
console.log(person.hobbies[0].toUpperCase());
Но если у нашего объекта нет свойства «hobbies», то вместо желаемого результата мы получим ошибку. Чтобы избежать этого, мы можем использовать опциональную цепочку ‘?.’.
console.log(person.hobbies?.[0]?.toUpperCase());
Здесь мы добавили оператор ‘?.’ после свойства «hobbies» и индекса «[0]». Это означает, что если свойства «hobbies» или элемента с индексом «[0]» нет, то код не будет бросать ошибку, а вернет undefined.
Теперь наш код будет безопасно работать даже на объектах, которые не имеют нужных свойств.
Кроме того, опциональная цепочка ‘?.’ также может использоваться для вызова методов объектов, которые могут не существовать:
const person = name: ‘John’,
age: 25,
greet() console.log(`Привет, меня зовут $`);
>
>;
person.unknownMethod?.(); // код не будет бросать ошибку, если метод не существует
Здесь мы вызываем несуществующий метод «unknownMethod» с помощью опциональной цепочки ‘?.’.
Если метод не существует, код не будет бросать ошибку и выполнение программы продолжится.
Вот и все, друг! Теперь ты знаешь, что такое опциональная цепочка ‘?.’ и как использовать ее для безопасной работы с объектами в JavaScript.
Надеюсь, эта статья была полезной и интересной для тебя! Так что ставь лайк)
Опциональная цепочка ‘?.’
Опциональная цепочка ?. — это безопасный способ доступа к свойствам вложенных объектов, даже если какое-либо из промежуточных свойств не существует.
Проблема «несуществующего свойства»
Если вы только начали читать учебник и изучать 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 приведёт к ошибке:
Введение в optional chaining operator
Длинные цепочки доступа к свойствам в JavaScript могут быть подвержены ошибкам, так как любой из них может иметь значение null или undefined (также известный как «нулевые» значения). Проверка существования свойства на каждом шаге легко превращается в глубоко вложенную структуру if -statements или длинное условие в 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 = 'optional setting'; const optionLength = db?.user?.preferences?.[optionName].length;
Эта последняя форма также доступна для необязательного индексирования массивов, например:
const userIndex = 42; const userName = usersArray?.[userIndex].name;
Необязательный оператор связывания ?? может быть объединен с нулевым оператором объединения, когда требуется undefined значение не по умолчанию. Это обеспечивает безопасный глубокий доступ к свойству с указанным значением по умолчанию, обращаясь к общему варианту _.get использования, который ранее требовал пользовательских библиотек, таких как lodash:
const object = < id: 123, names: < first: 'Alice', last: 'Smith' >>; < // lodash: const firstName = _.get(object, 'names.first'); // → 'Alice' const middleName = _.get(object, 'names.middle', '(no middle name)'); // → '(no middle name)' >< // с новым оператором: const firstName = object?.names?.first ?? '(no first name)'; // → 'Alice' const middleName = object?.names?.middle ?? '(no middle name)'; // → '(no middle name)' >
Свойства необязательного оператора цепочки
Опциональный оператор связывания обладает несколькими интересными свойствами: короткое замыкание, стекирование и необязательное удаление. Давайте пройдемся по каждому из них на примере.
Короткое замыкание означает не оценку остальной части выражения, если дополнительный оператор цепочки возвращается рано:
db?.user?.grow(++age);
Стекирование означает, что к последовательности обращений к свойствам можно применить несколько необязательных операторов сцепления:
const firstNameLength = db.users?.[42]?.names.first.length;
Тем не менее, подумайте об использовании более одного необязательного оператора цепочки в одной цепочке. Если значение гарантированно не будет нулевым, то использование оператора ?. для доступа к свойствам не рекомендуется. В приведенном выше примере, db всегда можно определить, но db.users и db.users[42] может не существовать. Если в базе данных есть такой пользователь, names.first.length предполагается, что он всегда определен.
Необязательное удаление означает, что оператор delete может быть объединен с необязательной цепочкой:
delete db?.user;
Более подробную информацию можно найти в разделе «Семантика».
Оператор опциональной последовательности
Оператор опциональной последовательности ?. позволяет получить значение свойства, находящегося на любом уровне вложенности в цепочке связанных между собой объектов, без необходимости проверять каждое из промежуточных свойств в ней на существование. ?. работает подобно оператору . , за исключением того, что не выбрасывает исключение, если объект, к свойству или методу которого идёт обращение, равен null или undefined . В этих случаях он возвращает undefined .
Таким образом, мы получаем более короткий и понятный код при обращении к вложенным по цепочке свойствам объекта, когда есть вероятность, что какое-то из них отсутствует.
Интерактивный пример
Синтаксис
?.prop; obj?.[expr]; arr?.[index]; func?.(args);
Описание
Оператор опциональной последовательности предоставляет способ упростить доступ к значениям в цепочке объектов, когда возможно, что какое-то свойство (или метод) в ней равно undefined или null .
Для примера, создадим объект obj , имеющий вложенную структуру. Без оператора опциональной последовательности поиск глубоко расположенных подсвойств требует проверки всех промежуточных свойств на существование, например:
let nestedProp = obj.first && obj.first.second;
Если обращаться к obj.first.second без проверки obj.first , то, если свойство obj.first равно null или undefined , выбросится исключение TypeError .
Однако, с оператором опциональной последовательности ( ?. ) не требуется явно проверять ссылку на obj.first перед обращением к obj.first.second :
let nestedProp = obj.first?.second;
Если используется оператор ?. вместо . , JavaScript знает о необходимости проверки obj.first перед обращением к obj.first.second . Если значение obj.first равно null или undefined , выполнение выражения автоматически прекращается и возвращается undefined .
Это эквивалентно следующему (кроме создания временной переменной):
let temp = obj.first; let nestedProp = temp === null || temp === undefined ? undefined : temp.second;
Опциональная последовательность с вызовом функции
Вы можете использовать ?. , когда необходимо вызвать метод, которого может не существовать. Это может быть полезно, например, при использовании API, в котором метод может быть недоступен из-за устаревания или не поддерживаемости устройством пользователя.
Использование ?. с вызовом функции значит, что выполнение автоматически вернёт undefined , а не выбросит исключение, если метод не найден:
let result = someInterface.customMethod?.();
Примечание: Для существующего свойства, не являющегося функцией, использование конструкции x.y?.() всё равно выбросит TypeError исключение ( x.y не является функцией ).
Работа с колбэк-функциями и обработчиками событий
Если вы используете колбэк-функции или извлекаете методы объекта деструктурирующим присваиванием, Вы можете получить несуществующие значения, которые нельзя вызывать как функции до проверки на их существование. Используя оператор ?. , вы можете избежать лишних проверок:
// С использованием ES2019 function doSomething(onContent, onError) try // . делаем что-то с данными > catch (err) if (onError) // проверяем, существует ли onError onError(err.message); > > >
// С использованием оператора опциональной последовательности function doSomething(onContent, onError) try // . делаем что-то с данными > catch (err) onError?.(err.message); // не выбросит исключение, если onError равен undefined > >
Опциональные последовательности в выражениях
Вы также можете использовать оператор опциональной последовательности, когда обращаетесь к свойству с помощью скобочной нотации:
let nestedProp = obj?.["prop" + "Name"];
Примеры
Базовый пример
В этом примере производится обращение к свойству name элемента с ключом bar объекта Map . Элемент с таким ключом отсутствует, но исключение выброшено не будет; nameBar равен undefined .
let myMap = new Map(); myMap.set("foo", name: "baz", desc: "inga" >); let nameBar = myMap.get("bar")?.name;
Сокращённое выполнение
При использовании оператора опциональной последовательности в выражениях, где левая часть операнда равна null или undefined , выражение не будет выполнено. Например:
let potentiallyNullObj = null; let x = 0; let prop = potentiallyNullObj?.[x++]; console.log(x); // 0, т.к. x не был инкрементирован
Совместное использование операторов опциональной последовательности
Во вложенных объектах возможно использование оператора опциональной последовательности неограниченное количество раз:
let customer = name: "Carl", details: age: 82, location: "Paradise Falls", // точный адрес неизвестен >, >; let customerCity = customer.details?.address?.city; // … это также работает с вызовами функций let duration = vacations.trip?.getTime?.();
Использование с оператором ??
Оператор ?? может использоваться после опциональной последовательности для установления значения по умолчанию:
let customer = name: "Carl", details: age: 82 >, >; const customerCity = customer?.city ?? "Unknown city"; console.log(customerCity); // Unknown city
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # prod-OptionalExpression |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Github — Proposal-pipeline-operator
- TC39 proposals
- Nullish Coalescing Operator
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.
