Operatory ?? i ?. w JavaScript – nullish coalescing i optional chaining w praktyce (także z myślą o dostępności)
Nowoczesny JavaScript dostarcza dwóch operatorów, które upraszczają pracę z danymi mogącymi być null lub undefined: nullish coalescing (??) oraz optional chaining (?.). Oba pojawiły się w standardzie ES2020 i są szeroko wspierane w przeglądarkach oraz w TypeScript.
W artykule znajdziesz przegląd kluczowych zagadnień:
- czym dokładnie są operatory
??i?., - jak działają „pod maską”,
- czym
??różni się od||i kiedy używać którego, - jak łączyć
?.i??w zwięzłe, bezpieczne konstrukcje, - praktyczne przykłady z front‑endu oraz z obszaru dostępności (a11y),
- na co uważać, aby nie zamaskować realnych błędów w aplikacji.
1. Skąd się wzięły ?? i ?.?
JavaScript od zawsze pozwalał pisać kod, który „wybucha” w najmniej oczekiwanym momencie, gdy spróbujesz odwołać się do właściwości undefined lub null:
const user = null;
console.log(user.profile.name); // TypeError: Cannot read properties of null
Aplikacje webowe coraz częściej korzystają z następujących źródeł i mechanizmów, co zwiększa niepewność danych:
- zewnętrznych API,
- dynamicznych danych,
- złożonych konfiguracji (w tym ustawień dostępności użytkownika).
Nie możesz zakładać, że wszystkie dane zawsze istnieją. Wcześniej stosowało się zagnieżdżone instrukcje if lub sekwencje &&:
const theme = user && user.settings && user.settings.theme;
To działa, ale bywa mało czytelne i podatne na błędy. Aby rozwiązać ten problem, do języka dodano dwa operatori ułatwiające bezpieczny dostęp i sensowne domyślne wartości:
- Optional chaining
?.– bezpieczny dostęp do głębokich właściwości i funkcji, bez rzucania błędów przynull/undefined; - Nullish coalescing
??– ustawianie domyślnych wartości tylko wtedy, gdy coś jestnulllubundefined, a nie gdy jest „falsy” (np.0czy pusty string).
2. Nullish coalescing ?? – „mądrzejsze” wartości domyślne
Definicja i składnia
Operator ?? zwraca prawy operand wyłącznie wtedy, gdy lewy jest null lub undefined; w przeciwnym razie zwraca lewy operand.
Składnia:
wartosc = wyrazenieLewo ?? wyrazeniePrawo;
Prosty przykład ustawienia nazwy:
const name = userInput ?? 'Anonim';
Jeśli userInput jest null lub undefined, name przyjmie wartość 'Anonim'; w innym przypadku zachowa wartość userInput.
Różnica między ?? a ||
|| traktuje każdą wartość „falsy” (0, '', false, NaN, null, undefined) jako powód, by użyć prawej strony, natomiast ?? reaguje wyłącznie na null oraz undefined. To kluczowe m.in. dla 0 czy pustych stringów, które mogą być poprawnymi danymi.
Przykład – limit stron w paginacji
const config = { pageSize: 0 };
const sizeWithOr = config.pageSize || 10;
const sizeWithNullish = config.pageSize ?? 10;
console.log(sizeWithOr); // 10 (0 jest traktowane jako falsy)
console.log(sizeWithNullish); // 0 (0 NIE jest nullish)
W tym przykładzie || „zepsuje” wartość 0, traktując ją jak brak wartości; ?? zachowa 0, bo nie jest null ani undefined.
Kiedy używać ??, a kiedy ||?
Sięgnij po ??, gdy zależy Ci na realnym braku wartości (null/undefined) i świadomym zachowaniu danych. Oto typowe zastosowania:
- konfiguracje (limity, timeouty, itp.),
- dane numeryczne, gdzie
0jest poprawną wartością, - stringi, gdy pusty string ma znaczenie (np. „pusty tytuł” jako świadomy wybór).
Użyj ||, jeśli chcesz zareagować na dowolną wartość „falsy”. Najczęstsze scenariusze:
- brak loginu → wyświetl etykietę „Gość”,
- brak parametru → użyj domyślnego tekstu,
- wartość pusta/zerowa w UI → pokaż przyjazny fallback.
Przykład różnicy na stringach:
const labelFromApi = ''; // API zwróciło pusty string – świadomie
const labelOr = labelFromApi || 'Domyślna etykieta';
const labelNullish = labelFromApi ?? 'Domyślna etykieta';
console.log(labelOr); // "Domyślna etykieta"
console.log(labelNullish); // "" (pusty string zostaje zachowany)
Praktyczne przykłady
Domyślny tekst dla elementu interfejsu
const settings = {
fontScale: null,
highContrast: false,
};
const fontScale = settings.fontScale ?? 1.0; // 1.0 tylko jeśli null/undefined
const highContrast = settings.highContrast ?? true; // false zostaje zachowane, bo nie jest nullish
Parsowanie ustawień dostępności z API
const userA11y = apiResponse.accessibility ?? {};
const prefersReducedMotion = userA11y.prefersReducedMotion ?? false;
const prefersHighContrast = userA11y.prefersHighContrast ?? false;
Dzięki ?? zachowujesz rozróżnienie między „wartość świadomie ustawiona na false” a „brak wartości w ogóle”.
?? a operatory && / ||
Specyfikacja JS zabrania mieszania ?? bezpośrednio z && lub || bez nawiasów. Takie wyrażenia są niepoprawne składniowo:
// to jest niepoprawne składniowo:
a || b ?? c;
Zawsze dodawaj nawiasy, aby uniknąć niejednoznaczności:
a || (b ?? c);
(a ?? b) || c;
3. Optional chaining ?. – bezpieczne odwołania do zagnieżdżonych danych
Definicja i idea
Optional chaining ?. działa jak „bezpieczna kropka” lub „bezpieczne wywołanie funkcji” – gdy wartość po lewej jest null lub undefined, całe wyrażenie kończy się i zwraca undefined zamiast wyjątku.
Podstawowe formy składni
Możesz użyć optional chaining w trzech głównych formach:
obj?.prop // dostęp do właściwości
obj?.[expr] // dostęp z indeksem / wyrażeniem
obj.func?.(arg) // bezpieczne wywołanie funkcji
obj?.prop– próbuje odczytaćprop, ale tylko jeśliobjnie jest nullish,obj?.[expr]– to samo, ale gdy nazwa właściwości jest obliczana,obj.func?.(arg)– wywołuje funkcję tylko, jeśliobj.funcistnieje i nie jest nullish.
Jeżeli którykolwiek element w łańcuchu optional chaining jest null/undefined, całe wyrażenie zwraca undefined.
Przykład z głęboko zagnieżdżonym obiektem
Bez optional chaining:
const theme = user && user.settings && user.settings.preferences && user.settings.preferences.theme;
Z optional chaining:
const theme = user?.settings?.preferences?.theme;
Jeśli user lub settings lub preferences będzie null albo undefined, theme stanie się undefined, ale nie zostanie rzucony błąd.
Optional chaining z tablicami i indeksami
Dostęp do elementu tablicy, gdy nie masz pewności, czy tablica istnieje:
const firstItem = data?.items?.[0];
Jeśli data lub items są nullish, firstItem będzie undefined.
Optional chaining z funkcjami
Bezpieczne wywołanie funkcji, która może nie istnieć:
const logger = window.customLogger;
logger?.('Wiadomość testowa'); // wywoła tylko, jeśli logger jest funkcją
Zamiast:
if (typeof logger === 'function') {
logger('Wiadomość testowa');
}
Optional chaining skraca zapis i zwiększa czytelność kontroli istnienia funkcji.
Co dokładnie „przerywa” ?.?
Operator ?. działa jak bezpieczny przystanek:
- jeśli wartość po lewej jest
nulllubundefined, wyrażenie kończy się i zwracaundefined, - kolejne części łańcucha nie są już sprawdzane,
- nie jest rzucany żaden wyjątek.
Przykład:
const result = config?.user?.preferences?.getTheme?.();
Jeśli config.user jest undefined, interpreter zatrzyma się na config?.user i zwróci undefined dla całego wyrażenia.
4. Łączenie ?. i ?? – bezpieczne łańcuchy z domyślną wartością
Połączenie ?. z ?? daje zwięzłe i odporne konstrukcje: ?. bezpiecznie przerywa dostęp do brakujących właściwości, a ?? zapewnia sensowną wartość domyślną wyłącznie dla null/undefined.
Przykład – preferencje użytkownika z API
const theme = user?.settings?.preferences?.theme ?? 'light';
Jeśli któryś z elementów (user, settings, preferences) jest nullish, łańcuch po lewej zwróci undefined, a ?? 'light' wstawi domyślny motyw „light”.
Przykład – ustawienia dostępności
Załóżmy, że API może (ale nie musi) zwrócić ustawienia dostępności:
const a11y = user?.profile?.accessibility;
const prefersHighContrast = a11y?.highContrast ?? false;
const prefersReducedMotion = a11y?.reducedMotion ?? false;
Unikasz błędów „Cannot read properties of undefined” i zawsze masz sensowne domyślne (false), gdy brakuje konkretnych flag.
Bezpieczne wywołanie funkcji z domyślną wartością
const value = config?.parseValue?.(raw) ?? defaultValue;
Jeśli config lub parseValue są nullish, lewa strona zwróci undefined, a ?? defaultValue dostarczy wartość zastępczą.
5. Przykłady z front‑endu i dostępności (a11y)
Serwis poświęcony dostępności powinien szczególnie dbać o to, by błędy JS nie przerywały działania aplikacji, bo skutkiem mogą być:
- niedziałające klawiszowe skróty,
- niesprawne komponenty ARIA (dialogi, menu, zakładki),
- brak komunikatów dla czytników ekranu.
Bezpieczne ustawianie atrybutów ARIA
Wyobraź sobie, że masz konfigurowalny komponent przycisku:
const config = getButtonConfig(); // może zwrócić obiekt niepełny
const label = config?.aria?.label ?? 'Otwórz menu';
button.setAttribute('aria-label', label);
config?.aria?.label pozwala bezpiecznie pobrać głęboko zagnieżdżoną właściwość, a ?? 'Otwórz menu' zapewnia czytelny domyślny tekst – dzięki temu przycisk zawsze ma sensowne aria-label.
Obsługa preferencji użytkownika przeglądarki
Odczyt preferencji „reduced motion” lub „dark mode” z obiektu konfiguracji lub z localStorage:
const userSettings = maybeParseJSON(localStorage.getItem('userSettings'));
const prefersReducedMotion = userSettings?.a11y?.reducedMotion ?? false;
if (prefersReducedMotion) {
document.documentElement.classList.add('reduced-motion');
}
Jeśli userSettings to null albo brak w nim sekcji a11y, kod nadal działa poprawnie przy inicjalizacji.
Bezpieczne logowanie i telemetria
System logowania nie powinien blokować działania aplikacji. Użyj optional chaining, by łagodnie pominąć brakujące integracje:
window.appTelemetry?.logEvent?.('A11Y_BUTTON_CLICK', {
time: Date.now(),
});
Gdy appTelemetry lub logEvent nie są zainicjalizowane (np. offline, środowisko testowe), wywołanie zostanie pominięte bez błędu, a kluczowe funkcje dostępności działają dalej bez zakłóceń.
6. Typowe pułapki i dobre praktyki
Nie nadużywaj optional chaining
Optional chaining jest wygodny, ale łatwo go nadużyć. Przykład, który może maskować krytyczny błąd:
// ZŁE: może maskować poważny błąd w strukturze danych
const criticalValue = data?.very?.deep?.path?.to?.value;
Jeśli dana powinna zawsze istnieć, nadużycie ?. może „uciszyć” realny problem (np. błąd API lub parsowania). Zamiast tego rzuć kontrolowany błąd lub wyświetl komunikat użytkownikowi.
Optional chaining nie jest „if‑em” dla wszystkiego
?. sprawdza tylko nullish (null/undefined). Jeśli np. user.settings jest pustym obiektem, user.settings?.theme zwróci undefined – nie dlatego, że settings jest „złe”, ale dlatego, że theme nie istnieje. Warto łączyć ?. z walidacją danych i jasną logiką biznesową.
Uważaj na ?? z innymi operatorami logicznymi
Nie mieszaj ?? z &&/|| bez nawiasów – to niezgodne ze składnią JS. Pisz wprost:
const value = (input ?? defaultValue) || 'fallback';
7. Wsparcie w TypeScript i narzędziach
TypeScript od wersji 3.7 obsługuje zarówno ?., jak i ??. Możesz używać tych operatorów w kodzie TS, a kompilator (tsc/Babel) przekształci je do form zgodnych ze starszymi środowiskami. W nowoczesnych projektach front‑endowych z bundlerami i transpilatorami ?? i ?. są standardem de facto.
8. Podsumowanie praktyczne
Najważniejsze rzeczy do zapamiętania:
- ?? (nullish coalescing) – zwraca prawy operand tylko dla
null/undefined; nie traktuje0,''anifalsejako „braku wartości”; idealny do sensownych wartości domyślnych w konfiguracji i danych; - ?. (optional chaining) – bezpiecznie przechodzi po łańcuchach właściwości i wywołaniach funkcji; przy
null/undefinedzwracaundefinedzamiast wyjątku; dostępne formy:obj?.prop,obj?.[expr],obj.func?.(args); - razem – używaj wzorca
user?.settings?.theme ?? 'light'podczas pracy z danymi opcjonalnymi; szczególnie przydatne w kontekście dostępności, gdzie bezpieczny kod JS jest krytyczny.






