Skip to content

Latest commit

 

History

History
234 lines (146 loc) · 13.8 KB

File metadata and controls

234 lines (146 loc) · 13.8 KB

Опціональний ланцюжок '?.'

[recent browser="new"]

Опціональний ланцюжок ?. -- це безпечний спосіб доступу до вкладених властивостей об’єктів, навіть якщо проміжних властивостей не існує.

Проблема "відсутньої властивості"

Якщо ви тільки почали читати підручник і вивчати 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. І в деяких випадках, коли відсутність елемента для нас є нормою, ми хотіли б просто отримати .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.

let user = {}; // користувач без властивості "address"

alert(user.address ? user.address.street ? user.address.street.name : null : null);

Виглядає просто жахливо та незрозуміло.

Але не хвилюйтесь, існує кращий варіант реалізації такої задачі за допомогою логічного оператора &&:

let user = {}; // користувач без властивості "address"

alert( user.address && user.address.street && user.address.street.name ); // undefined (немає помилки)

Логічне "І" з ланцюжком властивостей гарантує нам, що всі вони існують (якщо ж ні -- обчислення припиняється), але й це все ще не ідеал.

Як ви бачите, імена властивостей досі дублюються в коді. В прикладі вище властивість user.address з'являється тричі.

Ось чому опціональний ланцюжок ?. був доданий в мову. Щоб розв'язати цю проблему раз і назавжди!

Опціональний ланцюжок

Опціональний ланцюжок ?. припиняє обчислення, якщо значення перед ?. є undefined або null, і повертає undefined.

Для стислості надалі в цій статті ми будемо говорити про значення, що воно "існує", якщо воно відрізняється від null чи undefined

Іншими словами, value?.prop:

  • працює як value.prop, якщо value існує,
  • інакше (коли value є undefined/null) воно повертає undefined.

Ось безпечний спосіб доступу до властивості user.address.street за допомогою ?.:

let user = {}; // користувач без властивості "address"

alert( user?.address?.street ); // undefined (немає помилки)

Такий код виглядає коротшим і чистішим, взагалі немає ніякого дублювання.

Ось приклад із document.querySelector:

let html = document.querySelector('.elem')?.innerHTML; // буде undefined, якщо немає елемента

Читання властивості "address" з user?.address спрацює навіть коли в змінній user зберігається зовсім не об’єкт:

let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined

Зверніть увагу, що синтаксис ?. робить необов’язковою тільки властивість перед ним, а не будь-яку наступну.

Наприклад в user?.address.street.name конструкція user?. дозволяє user залишатись null/undefined (і повертати undefined в такому випадку), але це працює тільки для user. Доступ до решти властивостей здійснюється звичайним способом. Якщо ми хочемо, щоб якась з них була необов’язковою, тоді конкретно для цієї властивості нам доведеться замінити . на ?..

Нам слід використовувати `?.` тільки в тих ситуаціях коли ми припускаємо, що значення може не існувати.

Наприклад, якщо за нашою логікою об’єкт `user` точно існує, але його властивість `address` є необов’язковою, тоді нам слід використовувати конструкцію `user.address?.street`. Проте аж ніяк не `user?.address?.street`.

Тоді якщо помилково змінна `user` виявиться пустою, ми побачимо програмну помилку і зможемо це виправити. В іншому випадку, якщо ми зловживаємо `?.`, помилки можуть замовчуватися там де це непотрібно й ускладнювати процес налагодження.

````warn header="Змінна перед ?. повинна бути оголошеною" Якщо змінної `user` взагалі не існує, тоді конструкція `user?.anything` видасть помилку:

// ReferenceError: user is not defined
user?.address;

Змінна обов’язково повинна бути оголошена (наприклад let/const/var user або як параметр функції). Опціональний ланцюжок працює тільки з чинними змінними.


## Скорочене обчислення

Як вже говорилось, `?.` негайно припиняє обчислення, якщо лівої частини не існує.

Таким чином, якщо є додаткові виклики функцій або операції праворуч від `?.`, вони не будуть виконані.

Наприклад:

```js run
let user = null;
let x = 0;

user?.sayHi(x++); // немає "user", отже до x++ обчислення не дійде

alert(x); // 0, значення не було збільшено
```

## Інші способи застосування: ?.(), ?.[]

Опціональний ланцюжок `?.` -- це не оператор, а спеціальна синтаксична конструкція, що також працює з функціями та квадратними дужками.

Наприклад, `?.()` використовується для виклику потенційно відсутньої функції.

В прикладі нижче не в усіх користувачів є метод `admin`:

```js run
let userAdmin = {
  admin() {
    alert("Я адміністратор");
  }
};

let userGuest = {};

*!*
userAdmin.admin?.(); // Я адміністратор
*/!*

*!*
userGuest.admin?.(); // нічого (немає такого методу)
*/!*
```

В обох випадках спочатку використовуємо крапку (`userAdmin.admin`) для доступу до властивості `admin`, оскільки об’єкт користувача точно існує, а це означає, що ми можемо звернутись до будь-якої його властивості.

Вже потім `?.()` перевіряє ліву частину: якщо функція `admin` існує, то вона виконається (у випадку з `userAdmin`). Інакше (для `userGuest`) обчислення припиниться без помилок.

Також існує синтаксис `?.[]`, якщо ми хочемо отримати доступ до властивості за допомогою квадратних дужок `[]`, а не через крапку `.`. Як і в решті випадків, такий спосіб дає змогу безпечно читати властивості об’єкта яких може не існувати.

```js run
let key = "firstName";

let user1 = {
  firstName: "Іван"
};

let user2 = null;

alert( user1?.[key] ); // Іван
alert( user2?.[key] ); // undefined
```

Ми також можемо використовувати `?.` з `delete`:

```js run
delete user?.name; // видалити user.name, якщо користувач існує
```

````warn header="Ми можемо використовувати `?.` для безпечного читання і видалення властивостей, але не для запису"
Опціональний ланцюжок `?.` не має сенсу у лівій частині присвоювання.

Наприклад:
```js run
let user = null;

user?.name = "Іван"; // Помилка, не спрацює
// це по суті те ж саме, що й undefined = "John"
```

Воно недостатньо «розумне» для цього.

Підсумки

Синтаксис опціонального ланцюжка ?. має три форми:

  1. obj?.prop -- повертає obj.prop, якщо існує obj, і undefined в іншому випадку.
  2. obj?.[prop] -- повертає obj[prop], якщо існує obj, і undefined в іншому випадку.
  3. obj.method?.() -- викликає obj.method(), якщо існує obj.method, в іншому випадку повертає undefined.

Як бачимо, всі вони прості та зрозумілі в використанні. ?. перевіряє ліву частину на рівність null/undefined і дозволяє продовжувати обчислення якщо це не так.

Ланцюжок ?. дозволяє без виникнення помилок звертатись до вкладених властивостей.

Однак, потрібно розумно застосовувати ?., тільки в тих випадках де допустимо, що ліва частина не існує. Щоб таким чином не приховувати потенційні помилки програмування.