23 lipca 2022 6 min. czytania

To jest natywny JavaScript? Prawda??

Moje pierwsze spotkanie z optional chaining i nullish coalescing operator.

Zaktualizowano: 23 lipca 2022
Znak zapytania ułożony z kropek na żółtym tle
Zdjęcie autorstwa Anna Shvets

Pierwszy raz, gdy użyłem frameworka Gatsby, zauważyłem dziwny fragment kodu.

src/components/seo.jsJSX
1const defaultTitle = site.siteMetadata?.title

Zastanawiałem się: “O co chodzi z tym znakiem zapytania po kropce? To jest jakaś specjalna składnia Gatsby?”. Wątpiłem w to, więc zacząłem szukać informacji. Po krótkich poszukiwaniach i googlowaniu głupich pytań, znalazłem termin, którego szukałem - optional chaining. Byłem zaskoczony - to jest natywny JavaScript. Składnia ta została zaprezentowana w ES2020 (w dniu, w którym piszę ten post jest na czwartym etapie procesu wdrażania). Najpierw przyjrzymy się kilku błędom, aby zobaczyć dlaczego jest użyteczna.

39nth Technical Committee (TC39) jest to grupa będąca częścią ECMA International, która zrzesza deweloperów, wdrożeniowców i akademików zajmujących się językiem JavaScript. Komitet ten współpracuje ze społecznością w utrzymywaniu i rozwijaniu specyfikacji ECMAScript (JavaScript jest zgodny z tą specyfikacją). Proces rozwoju specyfikacji ma cztery etapy. Czwarty etap oznacza, że funkcjonalność jest gotowa do umieszczenia w najbliższym projekcie specyfikacji.

Jeżeli nie jesteś kompletnym nowicjuszem JavaScript, prawdopodobnie widziałeś wiadomość jak ta: “Cannot read properties of undefined”. Zwykle oznacza, że próbujesz uzyskać dostęp do nieistniejącej wartości zagnieżdżonego obiektu. Odtwórzmy ten błąd, ale tym razem celowo. Wyobraź sobie, że chcesz wyświetlić dokładne informacje o komputerach - stacjonarnych i laptopach. Składają się prawie z tych samych części, ale niektóre laptopy nie mają dedykowanej karty graficznej - nie ma o niej informacji. Jeżeli zechcesz uzyskać do niej dostęp, otrzymasz nasz błąd.

JS
1const desktop = {
2 processor: {
3 manufacturer: 'Intel',
4 type: 'I7'
5 },
6 graphicsCard: {
7 manufacturer: 'Nvidia'
8 }
9 //...więcej komponentów
10}
11
12const laptop = {
13 processor: {
14 manufacturer: 'AMD',
15 type: 'Ryzen 5'
16 }
17 //...więcej komponentów bez karty graficznej
18}
19
20const info = laptop.graphicsCard.manufacturer
21//Uncaught TypeError: Cannot read properties of undefined (reading 'manufacturer')

Spróbujmy rozwiązać ten problem. Na początek dodajmy brakujące informacje.

JS
1const laptop = {
2 processor: {
3 manufacturer: 'AMD',
4 type: 'Ryzen 5'
5 },
6 graphicsCard: {
7 manufacturer: ''
8 }
9}
10
11const info = laptop.graphicsCard.manufacturer //Brak błędu. Zwraca ""

A co gdybyśmy nie mogli? Nie zawsze mamy kontrolę nad API. Innym problemem jest sytuacja, gdy obiekt ma więcej wartości - każda z nich to musiałby być pusty string, null, itp. Średni pomysł. JavaScript jest dynamicznym językiem programowania i wartości mogą być null albo undefined. Musimy radzić sobie z takimi sytuacjami. Możemy użyć wyrażeń logicznych, aby rozwiązać ten problem.

JS
1const laptop = {
2 processor: {
3 manufacturer: 'AMD',
4 type: 'Ryzen 5'
5 }
6}
7
8const info = laptop && laptop.graphicsCard && laptop.graphicsCard.manufacturer //undefined

Ten kod działa. Ale jest rozwlekły i niezgrabny. Chcemy się dostać do jednej wartości, a potrzebujemy dwóch operatorów logicznych. Wyobraź sobie, że jest ich wiele. Może składnia warunkowa pomoże.

JS
1const laptop = {
2 processor: {
3 manufacturer: 'AMD',
4 type: 'Ryzen 5'
5 }
6}
7
8const info = laptop
9 ? undefined
10 : laptop.graphicsCard
11 ? undefined
12 : laptop.graphicsCard.manufacturer //undefined

Pisałem coś o niezgrabnym kodzie? To wygląda nawet gorzej. Zagnieżdżony operator trójskładnikowy to rzadko jest dobry pomysł. Ale JavaScript ma przecież składnię do obsługi błędów. Spróbujmy złapać ten błąd.

JS
1const laptop = {
2 processor: {
3 manufacturer: 'AMD',
4 type: 'Ryzen 5'
5 }
6}
7
8let info
9try {
10 info = laptop.graphicsCard.manufacturer
11} catch (error) {
12 info = undefined //undefined
13}

To jest bardziej czytelne, ale nadal rozwlekłe. Definiujemy nowe zakresy pomiędzy klamrami i nie możemy użyć const. Pora rozwiązać ten problem jak prawdziwy programista JavaScript - użyjmy zewnętrznej biblioteki!

JS
1const R = require('ramda')
2
3const laptop = {
4 processor: {
5 manufacturer: 'AMD',
6 type: 'Ryzen 5'
7 }
8}
9
10const info = R.path(['graphicsCard', 'manufacturer'], laptop) //undefined

Ten fragment jest zwięzły i czytelny. Ale czy naprawdę potrzebujemy zewnętrznej biblioteki do czegoś tak podstawowego? Nie, już nie. Optional chaining przychodzi na ratunek!

JS
1const laptop = {
2 processor: {
3 manufacturer: 'AMD',
4 type: 'Ryzen 5'
5 }
6}
7
8const info = laptop?.graphicsCard?.manufacturer //undefined

Używając tego zapisu możesz próbować uzyskać dostęp do wartości, które mogą być niedostępne. Powyższy fragment nie wyrzuca błędu. Zwraca undefined. Jeżeli odniesienie jest null albo undefined, wyrażenie zwraca undefined. Myślę, że operator ?. jest zwięzły i intuicyjny. Jest jak pytanie: “czy istnieje producent (karty graficznej) w obiekcie laptop?”. Jeżeli tak - zwróć tę wartość. W inny wypadku zwróć undefined. Ten operator jest bardziej uniwersalny. Możesz użyć tej składni przy wywoływaniu funkcji.

JS
1const laptop = {
2 processor: {
3 manufacturer: 'AMD',
4 type: 'Ryzen 5'
5 }
6}
7
8const info = laptop.nonExistingMethod?.() //undefined

Obiekt nie ma żadnej metody. Mimo to fragment powyżej nie wyrzuca wyjątku. Zwraca undefined. Pójdźmy dalej - czy można użyć tego operatora z tablicami? Tak, można.

JS
1const laptop = {
2 ram: ['Kingston 8GB', 'Kingston 8GB']
3}
4
5const info = laptop.ram?.[3] //undefined

Mimo że nie ma czterech elementów w tablicy, kod nie zwraca błędu.

Nullish coalescing operator

Jeżeli “dodamy” kolejny znak zapytania do naszego operatora i usuniemy kropkę, otrzymamy nowy operator logiczny - nullish coalescing operator.

JS
1const laptop = {
2 processor: {
3 type: 'Ryzen 5'
4 }
5}
6
7const info = laptop.graphicsCard?.manufacturer ?? 'integrated' //integrated

Możesz interpretować powyższy fragment w ten sposób: "Jeżeli istnieje producent karty graficznej w obiekcie laptop, przypisz tę wartość do zmiennej info. W innym wypadku przypisz wyraz 'integrated'". Z poprzedniej sekcji wiemy, że optional chaining zwraca undefined. Następnie interpreter przechodzi do logicznego operatora. Nullish coalescing operator to specjalny przypadek logicznego OR. Zwraca prawą część wyrażenia, gdy lewa część jest nulllub undefined. Logiczne OR zwraca prawą część jeżeli po lewej stronie jest dowolna wartość falsy.

JS
1const a = null ?? 'default' //"default"
2const b = undefined ?? 'default' //"default"
3const c = '' ?? 'default' //""
4const d = NaN ?? 'default' //NaN - zwraca każdą wartość falsy
5
6const e = '' || 'default' //"default"
7const f = NaN || 'default' //"default"
8const g = 0 || 'default' //"default" - zwraca "default" dla każdego falsy

Takie zachowanie logicznego OR może prowadzić to niespodziewanych błędów. Na przykład, gdy chcemy domyślną liczbę, ale 0 też jest poprawną, spodziewaną wartością. Dlatego nullish coalescing operator jest przydatny. Jest bardziej restrykcyjny i pomaga uniknąć takich błędów. Ale nie zastępuje on logicznego OR. Nie możesz także łączyć go z innymi operatorami logicznymi bez nawiasów.

null || undefined ?? "default"

Powyższy kod zwraca błąd składni.

(null || undefined) ?? "default”

Powyższy kod jest poprawny i zwraca “default”

Bonus - logical nullish assignment

Zbierając informację do tego posta, znalazłem jeszcze jeden operator z podwójnym znakiem zapytania. “Dodając” znak równości do nullish coalescing operator uzyskamy logical nullish assignment. Łatwo wydedukować co robi. Zamiast zwracania, przypisuje konkretną wartość do x, jeżeli x jest nullish (null lub undefined).

JS
1const laptop = {
2 processor: 'Intel'
3}
4
5laptop.processor ??= 'AMD'
6laptop.graphicsCard ??= 'Nvidia'
7
8console.log(laptop.processor) //"Intel"
9console.log(laptop.graphicsCard) //"Nvidia"

To jest skrót wyrażenia z nullish coalescing operator. Te dwa wyrażenia są równoznaczne.

JS
1const laptop = {
2 processor: 'Intel'
3}
4
5laptop.graphicsCard ??= 'Nvidia'
6laptop.graphicsCard ?? (laptop.graphicsCard = 'Nvidia')
7//Powyższe dwa wyrażenia robią to samo
8
9console.log(laptop.graphicsCard) //"Nvidia"

Jeżeli chcesz więcej przykładów lub szczegółów, sprawdź MDN Web Docs:

Newsletter, który rozpala ciekawość💡

Subskrybuj mój newsletter, aby otrzymywać comiesięczną dawkę:

  • Nowości, przykładów, inspiracji ze świata front-end, programowania i designu
  • Teorii naukowych i sceptycyzmu
  • Moich ulubionych źródeł, idei, narzędzi i innych interesujących linków
Nie jestem nigeryjskim księciem, aby oferować ci okazje. Nie wysyłam spamu. Anuluj kiedy chcesz.

Pozostań ciekawy. Przeczytaj więcej

Babeczki z turkusowym lukrem udekorowane posypką21 października 20239 min. czytania

TypeScript - dekoratory

Udekorujmy nasz tor... kod używając dekoratorów TypeScript. Dekoratory są smacznym dodatkiem do klas i zapewniają składnię do metaprogramowania.

Czytaj wpis
Zużyty, czarno-biały klaps filmowy14 czerwca 20238 min. czytania

Podejmij akcję i naucz się GitHub Actions!

Programiści uwielbiają automatyzować rzeczy. Ale automatyzacja jest korzystna tylko wtedy, gdy zabiera mniej czasu niż zwraca. Dzięki GitHub Actions może osiągniemy ten zysk.

Czytaj wpis
Joker na szczycie rozsypanych kart19 kwietnia 20239 min. czytania

TypeScript - typy generyczne

Typy generyczne nie istnieją w JavaScripcie, ale są jednym z podstawowych konceptów w TypeScripcie. Oferują to co najlepsze z obu światów: elastyczność i bezpieczeństwo typów.

Czytaj wpis