Problem: zagnieżdżony dostęp jest kruchy
Sięganie do zagnieżdżonego obiektu działa dobrze, dopóki któregoś poziomu nie zabraknie:
user.address to undefined, a odczyt .city z undefined rzuca TypeError: Cannot read properties of undefined. Prawdziwe dane (odpowiedzi API, sparsowany JSON, zapytania do DOM) są pełne pól, które mogą istnieć albo nie, a pisanie zabezpieczeń dla każdego poziomu szybko robi się uciążliwe:
const city = user && user.address && user.address.city;
Przy dwóch poziomach jeszcze czytelne. Przy czterech bolesne. Operator ?. to czystsze rozwiązanie.
?. przerywa na null i undefined
Optional chaining odczytuje właściwość tylko wtedy, gdy wartość przed nim nie jest null ani undefined. Jeśli jest, całe wyrażenie daje undefined i się zatrzymuje.
Pierwsza linijka normalnie czyta name. Druga trafia na user.address, widzi undefined i się wycofuje: reszta łańcucha w ogóle się nie wykonuje. Trzecia bez ?. rzuciłaby błąd, bo .toUpperCase() zostałoby wywołane na undefined. Z ?. całość po cichu daje undefined.
Model myślowy: ?. znaczy "jeśli to, co przede mną, to null lub undefined, poddaj się i zwróć undefined, a w przeciwnym razie idź dalej".
Działa też na tablicach i wywołaniach funkcji
Trzy składnie, ta sama idea:
?.[...]do dostępu do tablic albo dynamicznych właściwości.?.()do wywołania czegoś, co może być funkcją, ale nie musi.?.namedo zwykłego dostępu do właściwości.
Wszystkie trzy przerywają tak samo. user?.notAMethod?.() nie rzuca błędu, mimo że notAMethod nie istnieje: drugie ?. widzi undefined i się zatrzymuje.
To szczególnie wygodne przy opcjonalnych callbackach:
Koniec z zabezpieczeniami if (typeof onDone === "function") przed każdym wywołaniem.
Przerwanie uruchamiają tylko null i undefined
To szczegół, na którym ludzie się potykają. ?. interesują tylko wartości nullish. Inne wartości falsy, czyli 0, "", false, NaN, to całkiem poprawne wartości, na których można kontynuować łańcuch (na tyle, na ile pozwala autoboxing):
data.count to 0, czyli wartość falsy, ale nie nullish, więc ?.toFixed(2) się na niej wykonuje i zwraca "0.00". Porównaj to z &&:
Wersja z && zwraca 0, bo data.count jest falsy i przerywa obliczanie. Wersja z ?. zwraca "0.00", bo 0 nie jest nullish. Jeśli naprawdę chcesz zatrzymać się na 0, && jest właściwe. Jeśli chcesz zatrzymać się tylko wtedy, gdy "wartości brakuje", potrzebujesz ?..
Miejsce ? ma znaczenie
?. chroni wartość przed sobą, a nie po sobie. Dlatego stawiaj go na poziomie, którego może brakować:
Tutaj działają wszystkie trzy, bo niczego naprawdę nie brakuje. Ale jeśli config.server może być undefined, potrzebujesz config.server?.host: ?. przed server by nie pomogło, bo problemem jest odczyt .host z brakującego server.
Dobra zasada: stawiaj ?. na każdym poziomie, na którym wartość przed nim naprawdę może być nullish. Rozsypywanie ?. przy każdej kropce "na wszelki wypadek" ukrywa błędy i zaśmieca kod.
Przypisanie przez ?. nie działa
Optional chaining służy tylko do odczytu. Nie możesz go użyć po lewej stronie przypisania:
user?.address?.city = "Paris"; // SyntaxError
To ma sens, jeśli się nad tym zastanowić: co miałoby znaczyć przypisanie do właściwości undefined? Jeśli chcesz ustawić wartość tylko wtedy, gdy rodzic istnieje, zapisz to wprost:
Kiedy po niego sięgać, a kiedy nie
?. błyszczy, gdy wartość jest naprawdę opcjonalna:
- Odpowiedzi API, w których pola mogą się pojawić albo nie.
- Wyszukiwania w DOM:
document.querySelector(".banner")?.remove(). - Callbacki, które mogą nie zostać przekazane:
options.onError?.(err). - Łańcuchy wywołań bibliotek, w których wyniki pośrednie mogą być
null.
To złe narzędzie, gdy wartość powinna istnieć zawsze. Sypanie ?., żeby uciszyć błędy w takim przypadku, zamienia głośny, łatwy do znalezienia błąd ("TypeError w linii 42") w cichy (zmienna tajemniczo równa undefined trzy funkcje dalej). Gdy coś musi tam być, pozwól, żeby rzuciło błąd: stack trace robi ci przysługę.
Realistyczny przykład
Wyciąganie wartości z odpowiedzi API, która może być niekompletna:
Każde ?. obsługuje jeden poziom niepewności. avatarUrl czysto kończy jako undefined. onClick?.() wywołuje handler, jeśli akurat istnieje.
Zwróć uwagę na ?? w linijce z displayName: to druga połowa tego wzorca. ?. daje undefined, gdy czegoś brakuje, a ?? pozwala podstawić wartość domyślną bez potykania się o poprawne wartości falsy, takie jak 0 czy "".
Dalej: nullish coalescing
?. i ?? to ta sama idea zastosowana do różnych problemów: oba traktują null i undefined jako "brak", a inne wartości falsy zostawiają w spokoju. W następnej części: jak ?? daje rozsądne wartości domyślne i dlaczego || od lat po cichu robi to źle.
Najczęściej zadawane pytania
Co robi ?. w JavaScript?
Operator ?. odczytuje właściwość, indeks tablicy lub metodę tylko wtedy, gdy wartość przed nim nie jest null ani undefined. Jeśli jest, całe wyrażenie zostaje przerwane i daje undefined zamiast rzucać błąd. user?.address?.city nie wywróci się, gdy brakuje user albo address.
Kiedy używać optional chaining w JavaScript?
Używaj ?., gdy wartość może legalnie nie istnieć: odpowiedzi API z opcjonalnymi polami, wyszukiwania w DOM, które mogą nie znaleźć węzła, callbacki, które mogą zostać przekazane albo nie. Nie używaj go do zamiatania pod dywan błędów, w których wartość powinna istnieć zawsze: wtedy jej brak to prawdziwa informacja, którą chcesz zobaczyć.
Jaka jest różnica między ?. a &&?
W większości przypadków dają ten sam wynik, ale ?. przerywa tylko na null lub undefined, a && na każdej wartości falsy, także 0, '' i false. obj && obj.count zwraca 0, gdy count to 0. obj?.count też zwraca 0, ale łańcuch zatrzymuje się czysto tylko na wartościach nullish.
Czy optional chaining działa z tablicami i wywołaniami funkcji?
Tak. arr?.[0] bezpiecznie odczytuje indeks tablicy, a fn?.() wywołuje funkcję tylko wtedy, gdy istnieje. Obie formy działają według tej samej zasady: jeśli wartość przed ?. to null lub undefined, wyrażenie daje undefined i nic więcej się nie wykonuje.