Nowoczesny JavaScript daje sporo konstrukcji, które skracają boilerplate i lepiej pokazują intencję. W tym artykule zebrałem 10 technik, które realnie poprawiają czytelność kodu i nie chodzi tutaj o bycie sprytnym za wszelką cenę. Zapis ma być krótszy tam, gdzie to pomaga osobie, która będzie czytać ten kod kiedyś po Tobie.
1. Optional chaining w JavaScript zamiast długich warunków
Dostęp do zagnieżdżonych właściwości, które mogą nie istnieć, to codzienność przy danych z API. Optional chaining rozwiązuje ten problem minimalistycznie — operatorem ?. postawionym w odpowiednich miejscach.
Działa też z metodami oraz tablicami:
Jeśli którykolwiek element jest null lub undefined, wyrażenie zwraca undefined zamiast rzucać błąd i jest to świetny zamiennik dla defensywnego łańcucha sprawdzeń. Pamiętaj tylko, by nie używać go bezrefleksyjnie, ponieważ jeśli dana właściwość musi istnieć, ciche undefined może ukryć realny problem z danymi.
Dwa niuanse. Po pierwsze, ?. skraca cały łańcuch: w zapisie user?.address.street sprawdzany jest wyłącznie user i gdy jest nullish, reszta wyrażenia w ogóle się nie wykonuje, ale gdy user istnieje, a address nie, poleci klasyczny TypeError. Znak zapytania stawiaj więc przy każdym ogniwie, które realnie może nie istnieć, ale nie ma sensu stawiać go odruchowo przy wszystkich. Po drugie, api?.getData?.() chroni tylko przed null/undefined — jeśli getData okaże się np. stringiem, TypeError i tak poleci, bo ?.() nie sprawdza, czy coś jest funkcją.
2. Nullish coalescing jako bezpieczniejszy fallback niż ||
Operator || ma problem: traktuje 0, '' i false jako falsy, więc gdy chcesz podstawić wartość domyślną tylko dla null lub undefined, potrafi po cichu nadpisać dane, które są jak najbardziej poprawne.
Różnica jest subtelna, ale bardzo istotna:
||— fallback gdy wartość jest falsy (0, '', false, null, undefined)??— fallback tylko gdy wartość jest null lub undefined
Najczęściej najlepiej działa to w parze z optional chaining:
Jest tu jedna pułapka składniowa: ?? nie łączy się bezpośrednio z || ani &&. Zapis a || b ?? c to SyntaxError — specyfikacja celowo wymusza jawne nawiasy ((a || b) ?? c), bo pierwszeństwo tych operatorów bywało źródłem trudnych do zauważenia błędów. Jeśli linter podkreśla Ci takie wyrażenie, to nie kaprys, tylko język ratujący Cię przed niejednoznaczną intencją.
3. Destructuring z domyślnymi wartościami w JavaScript
Destrukturyzacja szybko skraca kod, ale warto pamiętać, że sama w sobie nie chroni przed null i undefined.
Zauważ = {} na końcu, które jest zabezpieczeniem na wypadek, gdy funkcja zostanie wywołana bez argumentów.
Pamiętaj, że default w destrukturyzacji działa tylko dla undefined, a nie dla null, więc jeśli chcesz fallback również dla null, użyj ?? albo zastosuj user ?? {} jak w przykładzie wyżej.
4. Object shorthand w JavaScript i mniej powtarzalnego kodu
Masz zmienną i chcesz umieścić ją w obiekcie pod dokładnie taką samą nazwą, jaką już nosi? Zamiast pisać name: name, JavaScript pozwala to zapisać krócej, kiedy nazwa zmiennej jest taka sama jak klucz obiektu:
Działa także z metodami:
Niby nic, ale w return, payloadach i konfiguracjach ten zapis pojawia się cały czas. Po wprowadzeniu tego uproszczenia, znacznie łatwiej wyłapać naprawdę istotne rzeczy.
Z tej samej rodziny składni pochodzą computed property names — gdy nazwa klucza siedzi w zmiennej, nie musisz budować obiektu w dwóch krokach:
Najbardziej pożyteczny jest shorthand tam, gdzie budujesz obiekt z kilkunastu zmiennych naraz, na przykład zbierając dane z formularza albo przygotowując payload do wysłania na backend. Powtórzenie name: name w jednej linijce specjalnie nie przeszkadza, ale w obiekcie z dziesięcioma polami każde takie powtórzenie to dodatkowa okazja do potknięcia (gdy ktoś zmieni nazwę zmiennej w jednym miejscu, a zapomni w drugim). Shorthand eliminuje ten problem, ponieważ zostaje tylko jedna nazwa do pilnowania.
5. Spread operator do klonowania i łączenia danych
Klonowanie obiektu, łączenie kilku obiektów w jeden czy sklejanie tablic to czynności, które kiedyś wymagały Object.assign() albo concat(). W większości codziennych przypadków spread jest najczytelniejszym sposobem na skopiowanie lub złożenie danych:
Pamiętaj, że spread robi shallow copy, czyli kopiuje tylko pierwszy poziom:
structuredClone() może pomóc przy głębszym kopiowaniu prostych struktur danych, ale nie jest uniwersalnym zamiennikiem dla każdego obiektu:
Nie sklonujesz tak np. funkcji, a instancje własnych klas stracą prototyp i metody zdefiniowane na klasie.
6. Metody tablic w JavaScript zamiast ręcznych pętli
Gdy transformujesz dane, metody tablicowe najczęściej (choć nie zawsze) lepiej pokazują intencję niż ręczna pętla:
Inne przydatne metody:
Nie traktuj tego kategorycznie, ponieważ jeśli potrzebujesz break, continue, wielu efektów ubocznych albo sekwencyjnego await, zwykłe for...of często będzie czytelniejsze niż wciskanie wszystkiego na siłę w map() czy też reduce().
7. Template literals zamiast konkatenacji stringów
To jedna z tych zmian, które po prostu warto wprowadzić na stałe, ponieważ interpolacja stringów jest czytelniejsza niż +:
W tym wypadku, prawdziwa moc to multiline strings i wyrażenia:
Najlepiej sprawdzają się w komunikatach, URL-ach, klasach CSS oraz prostych szablonach, a jeśli budujesz duże fragmenty HTML w aplikacji React, zwykle lepiej wejść poziom wyżej i renderować JSX zamiast składać stringi ręcznie.
Konkatenacja przez + ma wadę, ponieważ przy dłuższych komunikatach trzeba ręcznie pilnować spacji i cudzysłowów wokół każdej zmiennej, więc literówka w stylu brakującej spacji przed zmienną potrafi się wkraść niepostrzeżenie i ujawnić dopiero na produkcji. Template literal eliminuje ten problem, bo tekst i zmienne są zapisane dokładnie tak, jak mają wyglądać w finalnym stringu — nie trzeba w głowie składać fragmentów z powrotem w całość, żeby zweryfikować, czy wynik ma sens.
8. Logical assignment operators w JavaScript
Wzorzec „przypisz wartość tylko wtedy, gdy jeszcze jej nie ma" pojawia się w kodzie na tyle często, że doczekał się własnej, krótszej składni. Zamiast if sprawdzającego warunek przed przypisaniem, masz do dyspozycji nowe operatory przypisania z logiką warunkową:
Trzy operatory:
||=— przypisz jeśli falsy ('', 0, false, null, undefined)&&=— przypisz jeśli truthy??=— przypisz jeśli null/undefined
To bardzo wygodny zapis przy inicjalizacji danych i normalizacji payloadów, ale uważaj tylko tam, gdzie mutacja obiektu jest niepożądana, ponieważ te operatory nadal modyfikują istniejącą referencję.
9. Object.entries() i Object.fromEntries() do transformacji obiektów
Obiekty nie mają wbudowanych metod typu map() czy filter(), więc ich przekształcanie zwykle wymagało pętli for...in albo ręcznego budowania nowego obiektu. Object.entries() i Object.fromEntries() zamieniają ten problem w operację na zwykłej tablicy par klucz-wartość, dzięki czemu przekształcanie obiektów staje się wtedy banalnie proste:
Bardzo dobrze sprawdza się, gdy używasz ich z metodami tablicowymi:
Ten wzorzec przydaje się wszędzie tam, gdzie obiekt pełni funkcję mapy klucz-wartość, a nie modelu z konkretnymi polami. Na przykład ceny per produkt, tłumaczenia per język albo feature flagi per użytkownik. Bez Object.entries() transformacja takiego obiektu zwykle kończy się ręczną pętlą for...in z mutowaniem nowego obiektu w środku, co jest bardziej podatne na pomyłki niż złożenie map()/filter() z fromEntries(). Warto tylko pamiętać, że kolejność kluczy w wynikowym obiekcie odpowiada kolejności z tablicy wejściowej, więc jeśli kolejność ma znaczenie w UI, ten wzorzec ją zachowuje.
10. Array.at() i czytelne indeksowanie tablic
Ostatni element tablicy potrzebujesz w kodzie zaskakująco często, a klasyczny zapis przez arr.length - 1 zawsze wymaga chwili zastanowienia, o ile trzeba się cofnąć. Array.at() rozwiązuje to przez dostęp do elementów od końca tablicy za pomocą ujemnego indeksu:
Mała rzecz, a cieszy, bo poprawia czytelność wszędzie tam, gdzie często sięgasz po ostatni element, czyli w historii, breadcrumbsach, kolejkach czy wynikach sortowania. Działa też ze stringami.
Zapis arr[arr.length - 1] ma jeszcze jedną wadę poza długością. Łatwo pomylić się o jeden indeks, zwłaszcza przy arr.length - 2 czy głębszym cofaniu się od końca, a taki błąd nie rzuca wyjątku, tylko po cichu zwraca undefined albo nieprawidłowy element. at(-1) i at(-2) czytają się dokładnie tak, jak brzmi intencja („ostatni" i „przedostatni"), więc nie ma miejsca na pomyłkę w arytmetyce indeksów.
Bonus: Object.groupBy() w ES2024
Grupowanie listy elementów według wspólnej cechy to jedno z tych zadań, które przed ES2024 zawsze kończyły się napisaniem tego samego reduce() od nowa albo sięgnięciem po bibliotekę typu Lodash. Object.groupBy() załatwia to natywnie, bez zewnętrznych bibliotek:
To standard ES2024, więc jeśli wspierasz starsze środowiska, sprawdź kompatybilność albo dodaj polyfill.
Nie chodzi o to, że reduce() nagle stał się złym rozwiązaniem, ale po prostu w wielu przypadkach Object.groupBy() mówi wprost, co robisz, więc daje lepszą czytelność. Klasyczna wersja przez reduce() wymaga za każdym razem tego samego boilerplate'u — sprawdzenia, czy klucz już istnieje w akumulatorze, i inicjalizacji pustej tablicy, jeśli nie — a ta logika w niczym nie zależy od konkretnego przypadku użycia, więc jej powtarzanie w każdym miejscu, gdzie grupujesz dane, tylko rozmywa właściwą intencję kodu.
Uzupełniając, gdy kluczem grupowania ma być coś innego niż string lub symbol (na przykład cały obiekt) użyj bliźniaczego Map.groupBy(), które zwraca Map i nie rzutuje kluczy na stringi.
toSorted() zamiast mutowania tablicy
W komponentach React łatwo przypadkiem posortować tablicę pochodzącą z propsów albo stanu. sort() zmienia oryginał, natomiast toSorted() zwraca nową tablicę.
Ten błąd jest podstępny, bo users.sort(...) w środku komponentu wygląda niewinnie, ale mutuje dokładnie tę samą tablicę, którą trzyma stan albo rodzic przekazujący propsy. Efekt bywa taki, że komponent renderuje się poprawnie za pierwszym razem, a przy kolejnym renderze coś przestaje się zgadzać. Dla przykładu React nie wykrywa zmiany, bo referencja do tablicy jest wciąż ta sama, mimo że jej zawartość się zmieniła. toSorted() rozwiązuje to przy okazji, bo zwraca nowy obiekt tablicy, więc oryginalne dane zostają nietknięte, a zmiana jest jawna i przewidywalna.
To niewielka zmiana składni, ale lepiej komunikuje intencję: wynik ma być posortowaną kopią, a dane wejściowe pozostają bez zmian. toSorted() ma zresztą całe rodzeństwo z ES2023 — toReversed(), toSpliced() i with() — wszystkie zwracają nową tablicę zamiast mutować oryginał, więc pasują do tego samego, immutable stylu pracy z danymi.
