Необов'язкове ланцюгування

This is an experimental technology
Check the Browser compatibility table carefully before using this in production.

Оператор необов'язкового ланцюгування ?. дозволяє читати значення властивості, розташованої глибоко всередині ланцюга пов'язаних об'єктів, без необхідності прямо підтверджувати, що кожне посилання у ланцюгу є дійсним. Оператор ?. діє схоже з оператором ланцюгування ., за винятком того, що замість помилки у випадку посилання на null чи undefined, вираз виконає коротке замикання та поверне undefined. При використанні з викликами функцій, вираз поверне undefined, якщо задана функція не існує.

Це створює коротші та простіші вирази для звернення через ланцюг властивостей, коли існує ймовірність зустріти відсутнє посилання. Це також може бути корисним для дослідження вмісту об'єкта, коли немає гарантії того, що певні властивості є обов'язковими.

Синтаксис

obj?.prop
obj?.[expr]
func?.(args)

Опис

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

Наприклад, розглянемо об'єкт obj, який має вкладену структуру. Без необов'язкового ланцюгування пошук глибоко вкладеної властивості вимагає перевірки проміжних посилань, як от:

let nestedProp = obj.first && obj.first.second;

Підтверджуємо, що значення obj.first не null (і не undefined) перед тим, як звертатись до obj.first.second. Це дозволяє запобігти помилці, яка виникла б, якщо б ми просто звернулись прямо до obj.first.second без перевірки obj.first.

Проте, з оператором необов'язкового ланцюгування (?.) ви не мусите робити явну перевірку та переривати вираз в залежності від стану obj.first перед тим, як звертатись до obj.first.second:

let nestedProp = obj.first?.second;

При використанні оператора ?. замість простого . JavaScript виконує неявну перевірку, щоб переконатись, що obj.first не дорівнює null чи undefined, перед тим, як звертатись до obj.first.second. Якщо obj.first дорівнює null чи undefined, вираз автоматично виконує коротке замикання, повертаючи undefined.

Це еквівалентно наступному:

let nestedProp = ((obj.first == null || obj.first == undefined) ? undefined : obj.first.second);

Необов'язкове ланцюгування з викликами функцій

Ви можете скористатись необов'язковим ланцюгуванням під час виклику методу, який, можливо, не існує. Це може бути зручно, наприклад, при використанні API, у якому метод може бути недоступний, чи то через застарілу реалізацію, чи тому, що функціональність недоступна на пристрої користувача.

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

let result = someInterface.customMethod?.();

Заувага: Якщо існує властивість з таким іменем, яка не є функцією, використання ?. все ж спричинить виняток TypeError (x.y is not a function).

Необов'язкові зворотні виклики та обробники подій

Якщо ви використовуєте зворотні виклики або методи fetch з об'єкта з деструктуризаційним присвоєнням, ви, можливо, матимете неіснуючі значення, які не можна викликати як функції, поки ви не перевірили, що вони існують. Використовуючи ?., можна уникнути цієї додаткової перевірки:

// Написано згідно 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 у мапі, де немає такого ключа. Отже, результатом буде 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: "Карл",
  details: {
    age: 82,
    location: "Далекі гори" // точна адреса невідома
  }
};
let customerCity = customer.details?.address?.city;

// … це також працює для ланцюгування виклику функції
let duration = vacations.trip?.getTime?.();

Поєднання з оператором null-об'єднання

Можна скористатись оператором null-об'єднання після необов'язкового ланцюгування, щоб задати значення, коли воно не було знайдене:

let customer = {
  name: "Карл",
  details: { age: 82 }
};
let customerCity = customer?.city ?? "Невідоме місто";
console.log(customerCity); // Невідоме місто

Специфікації

Специфікація Статус Коментар
Пропозиція щодо оператора "необов'язкового ланцюгування" Стадія 3

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
Optional chaining operator (?.)
Experimental
Chrome Full support 79
Disabled
Full support 79
Disabled
Disabled From version 79: this feature is behind the Experimental JavaScript preference (needs to be set to true). To change preferences in Chrome, visit chrome://flags.
Edge No support NoFirefox No support NoIE No support NoOpera Full support 65
Disabled
Full support 65
Disabled
Disabled From version 65: this feature is behind the Experimental JavaScript preference (needs to be set to true).
Safari No support NoWebView Android No support NoChrome Android Full support 79
Disabled
Full support 79
Disabled
Disabled From version 79: this feature is behind the Experimental JavaScript preference (needs to be set to true). To change preferences in Chrome, visit chrome://flags.
Firefox Android No support NoOpera Android No support NoSafari iOS No support NoSamsung Internet Android No support Nonodejs No support No

Legend

Full support  
Full support
No support  
No support
Experimental. Expect behavior to change in the future.
Experimental. Expect behavior to change in the future.
User must explicitly enable this feature.
User must explicitly enable this feature.

Хід реалізації

Наведена нижче таблиця надає щоденний статус реалізації цієї функціональності, оскільки функціональність ще не досягла кросбраузерної стабільності. Дані генеруються запуском відповідних тестів функціональності у Test262, стандартному тестовому наборі JavaScript, на нічній збірці чи на останньому релізі рушія JavaScript кожного веб-переглядача.

Див. також