7 wskazówek dotyczących obsługi niezdefiniowanych w JavaScript
Większość współczesnych języków, takich jak Ruby, Python czy Java, ma jedną wartość null (nil
lub null
), co wydaje się rozsądnym podejściem.
Ale JavaScript jest inny.
null
, ale także undefined
, reprezentują w JavaScript puste wartości. Jaka jest więc dokładna różnica między nimi?
Krótka odpowiedź jest taka, że interpreter JavaScript zwraca undefined
podczas uzyskiwania dostępu do zmiennej lub właściwości obiektu, która nie została jeszcze zainicjowana. Na przykład:
Z drugiej strony null
reprezentuje brakujący obiekt odniesienie. JavaScript nie inicjalizuje zmiennych ani właściwości obiektów za pomocą null
.
Niektóre metody natywne, takie jak String.prototype.match()
, mogą zwracać null
w celu oznaczenia brakującego obiektu. Spójrz na przykład:
Ponieważ JavaScript jest przyzwalający, programiści mają pokusę dostępu do niezainicjowanych wartości. Ja też jestem winny takiej złej praktyki.
Często takie ryzykowne działania generują undefined
powiązane błędy:
-
TypeError: "undefined" is not a function
-
TypeError: Cannot read property "<prop-name>" of undefined
- i podobne błędy pisarskie.
Programista JavaScript może zrozumieć ironię tego żartu :
Aby zredukować takie błędy, musisz zrozumieć przypadki, w których undefined
jest generowany. Zbadajmy undefined
i jego wpływ na bezpieczeństwo kodu.
1. Co jest niezdefiniowane
JavaScript ma 6 typów pierwotnych:
I oddzielny typ obiektu: {name: "Dmitri"}
, .
Z 6 typów pierwotnych undefined
jest wartością specjalną z własnym typem Niezdefiniowanym. Zgodnie ze specyfikacją ECMAScript:
Wartość pierwotna niezdefiniowanej wartości jest używana, gdy zmiennej nie przypisano wartości.
Norma jasno określa, że undefined
otrzymasz podczas uzyskiwania dostępu do niezainicjowanych zmiennych, nieistniejących właściwości obiektów, nieistniejące elementy tablicy i tym podobne.
Kilka przykładów:
Powyższy przykład pokazuje, że dostęp do:
- niezainicjowanej zmiennej
number
- nieistniejąca właściwość obiektu
movie.year
- lub nieistniejący element tablicy
movies
są oceniane jako undefined
.
Specyfikacja ECMAScript definiuje typ undefined
wartości:
Niezdefiniowany typ to typ, którego jedyną wartością jest
undefined
wartość.
W tym sensie zwraca ciąg "undefined"
dla wartości undefined
:
Oczywiście typeof
dobrze sprawdza się w celu sprawdzenia, czy zmienna zawiera wartość undefined
:
2. Scenariusze powodujące tworzenie niezdefiniowanych
2.1 Niezainicjowana zmienna
Zadeklarowana zmienna, ale nie została jeszcze przypisana wartość (niezainicjowana), jest domyślnie
undefined
.
Zwykłe i proste:
myVariable
został zadeklarowany i nie ma jeszcze przypisanej wartości. Dostęp do zmiennej daje undefined
.
Efektywnym podejściem do rozwiązywania problemów związanych z niezainicjowanymi zmiennymi jest, gdy tylko jest to możliwe, przypisanie wartości początkowej. Im mniej zmienna istnieje w niezainicjowanym stanie, tym lepiej.
Idealnie byłoby przypisać wartość zaraz po zadeklarowaniu const myVariable = "Initial value"
. Ale to nie zawsze jest możliwe.
Wskazówka 1: Faworyzuj const
, w przeciwnym razie użyj let
, ale pożegnaj się z var
Moim zdaniem jedną z najlepszych cech ECMAScript 2015 jest nowy sposób deklarowania zmiennych przy użyciu const
i let
. To duży krok naprzód.
const
i let
mają zasięg blokowy (w przeciwieństwie do starszej funkcji o zakresie var
) i istnieją w czasowej martwej strefie aż do wiersza deklaracji.
Polecam zmienną const
, gdy jej wartość nie ulegnie zmianie. Tworzy niezmienne powiązanie.
Jedną z fajnych cech const
jest to, że musisz przypisać wartość początkową do zmiennej const myVariable = "initial"
. Zmienna nie jest wystawiona na stan niezainicjowany i dostęp do undefined
jest niemożliwy.
Sprawdźmy funkcję sprawdzającą, czy słowo jest palindromem:
length
i half
zmiennym przypisuje się wartość raz. Wydaje się rozsądne zadeklarowanie ich jako const
, ponieważ te zmienne się nie zmienią.
Użyj deklaracji let
dla zmiennych, których wartość może się zmieniać. Jeśli to możliwe, od razu przypisz wartość początkową, np. let index = 0
.
A co ze starą szkołą var
? Proponuję przestać go używać.
var
Problem z deklaracją polega na podnoszeniu zmiennej w zakresie funkcji. Możesz zadeklarować zmienną var
gdzieś na końcu zakresu funkcji, ale nadal możesz uzyskać do niej dostęp przed deklaracją: i otrzymasz undefined
.
myVariable
jest dostępny i zawiera undefined
nawet przed wierszem deklaracji: var myVariable = "Initial value"
.
W przeciwieństwie do zmiennej const
lub let
nie można uzyskać dostępu przed wierszem deklaracji – zmienna znajduje się w czasowej martwej strefie przed deklaracją. I to jest fajne, ponieważ masz mniejsze szanse na dostęp do undefined
.
Powyższy przykład zaktualizowany o let
(zamiast of var
) generuje ReferenceError
, ponieważ zmienna w czasowej martwej strefie jest niedostępna.
Zachęcanie do używania const
w przypadku niezmiennych powiązań lub let
w inny sposób zapewnia praktykę, która zmniejsza widoczność niezainicjowanych zmienna.
Wskazówka 2: Zwiększ spójność
Spójność charakteryzuje stopień, w jakim elementy modułu (przestrzeń nazw, klasa, metoda, blok kodu) należą do siebie. Spójność może być wysoka lub niska.
Moduł o wysokiej spójności jest preferowany, ponieważ elementy takiego modułu koncentrują się wyłącznie na jednym zadaniu. To sprawia, że moduł:
- Skoncentrowany i zrozumiały: łatwiejszy do zrozumienia, co robi moduł
- Konserwowalny i łatwiejszy do refaktoryzacji: zmiana w module wpływa na mniejszą liczbę modułów
- Wielokrotnego użytku: skupienie się na jednym zadaniu sprawia, że moduł jest łatwiejszy do ponownego wykorzystania
- Testowalny: łatwiej byłoby przetestować moduł skoncentrowany na jednym zadaniu
Cechą dobrze zaprojektowanego systemu jest wysoka spójność, której towarzyszy luźne sprzężenie.
Blok kodu można uznać za mały moduł. Aby skorzystać z zalet wysokiej spójności, należy utrzymywać zmienne jak najbliżej bloku kodu, który ich używa.
Na przykład, jeśli zmienna istnieje wyłącznie po to, aby utworzyć logikę zakresu blokowego, zadeklaruj i uaktywnij zmienną tylko w tym bloku (używając const
lub let
deklaracje). Nie ujawniaj tej zmiennej zewnętrznemu zakresowi bloku, ponieważ zewnętrzny blok nie powinien przejmować się tą zmienną.
Klasycznym przykładem niepotrzebnie wydłużonego czasu życia zmiennych jest użycie for
cykl wewnątrz funkcji:
index
, item
i zmienne są deklarowane na początku treści funkcji. Jednak są one używane tylko pod koniec. Jaki jest problem z tym podejściem?
Między deklaracją u góry a użyciem w instrukcji for
zmienne index
, item
są niezainicjowane i widoczne dla undefined
. Mają nieracjonalnie długi cykl życia w całym zakresie funkcji.
Lepszym podejściem jest przeniesienie tych zmiennych jak najbliżej miejsca ich użycia:
index
i istnieją tylko w zakresie blokowym instrukcji for
. Nie mają one żadnego znaczenia poza for
.
Zmienna length
jest również zadeklarowana blisko źródła jej użycia.
Dlaczego zmodyfikowana wersja jest lepsza od początkowej? Zobaczmy:
- Zmienne nie są narażone na stan niezainicjowania, więc nie ma ryzyka dostępu do
undefined
- Przenoszenie zmienne jak najbliżej miejsca ich użycia zwiększają czytelność kodu
- Wysoko spójne fragmenty kodu są łatwiejsze do refaktoryzacji i wyodrębniania do oddzielnych funkcji, jeśli to konieczne
2.2 Dostęp nieistniejąca właściwość
Podczas uzyskiwania dostępu do nieistniejącej właściwości obiektu JavaScript zwraca
undefined
.
Pokażmy to na przykładzie:
favoriteMovie
to obiekt z jedną właściwością title
. Dostęp do nieistniejącej właściwości actors
przy użyciu metody dostępu do właściwości favoriteMovie.actors
daje wynik undefined
.
Uzyskanie dostępu do nieistniejącej właściwości nie powoduje błędu. Problem pojawia się podczas próby pobrania danych z nieistniejącej właściwości, która jest najczęstszą pułapką undefined
, odzwierciedloną w dobrze znanym komunikacie o błędzie TypeError: Cannot read property <prop> of undefined
.
Nieznacznie zmodyfikujmy poprzedni fragment kodu, aby zilustrować TypeError
rzut:
favoriteMovie
nie ma właściwości actors
, więc favoriteMovie.actors
zwraca undefined
.
W rezultacie uzyskanie dostępu do pierwszego elementu wartości undefined
przy użyciu wyrażenia favoriteMovie.actors
powoduje wyświetlenie TypeError
.
Pozwalający charakter JavaScript, który umożliwia dostęp do nieistniejących właściwości, jest źródłem niedeterminizmu: właściwość może być ustawiona lub nie. Dobrym sposobem na obejście tego problemu jest ograniczenie obiektu tak, aby zawsze miał zdefiniowane właściwości, które posiada.
Niestety, często nie masz kontroli nad obiektami. Takie obiekty mogą mieć inny zestaw właściwości w różnych scenariuszach. Musisz więc obsługiwać wszystkie te scenariusze ręcznie.
Zaimplementujmy funkcję append(array, toAppend)
, która dodaje na początku i / lub na końcu tablicy nowych elementów. Parametr toAppend
akceptuje obiekt z właściwościami:
-
first
: element wstawiony na początkuarray
-
last
: element wstawiony na końcuarray
.
Funkcja zwraca nową instancję tablicy, bez zmiany oryginalnej tablicy.
Pierwsza wersja append()
, nieco naiwna, może wyglądać tak:
Ponieważ toAppend
może pomijać właściwości first
lub last
, obowiązkowe jest sprawdzenie, czy te właściwości istnieją w toAppend
.
Metoda dostępu do właściwości zwraca wartość undefined
, jeśli właściwość nie istnieje. Pierwszą pokusą sprawdzenia, czy właściwości first
lub last
są obecne, jest sprawdzenie ich pod kątem undefined
. Odbywa się to w poleceniach warunkowych if(toAppend.first){}
i if(toAppend.last){}
…
Nie tak szybko. Takie podejście ma wadę. undefined
, a także false
, null
, 0
, NaN
i ""
to wartości fałszywe.
W obecnej implementacji append()
funkcja nie pozwala na wstawianie fałszywych elementów:
Poniższe wskazówki wyjaśniają, jak poprawnie sprawdzić istnienie nieruchomości.
Wskazówka 3: Sprawdź istnienie nieruchomości
Na szczęście JavaScript oferuje kilka sposobów określenia, czy obiekt ma określoną właściwość:
-
obj.prop !== undefined
: porównaj zundefined
bezpośrednio -
typeof obj.prop !== "undefined"
: sprawdź typ wartości właściwości -
obj.hasOwnProperty("prop")
: sprawdź, czy obiekt ma własną właściwość -
"prop" in obj
: sprawdź, czy obiekt ma własną czy dziedziczoną właściwość
Zalecam użyj operatora in
. Ma krótką i słodką składnię. Obecność operatora in
sugeruje wyraźny zamiar sprawdzenia, czy obiekt ma określoną właściwość, bez uzyskiwania dostępu do rzeczywistej wartości właściwości.
obj.hasOwnProperty("prop")
to również fajne rozwiązanie. Jest nieco dłuższy niż operator in
i weryfikuje tylko we własnych właściwościach obiektu.
Poprawmy funkcję append(array, toAppend)
za pomocą operatora in
:
"first" in toAppend
(i "last" in toAppend
) to true
czy odpowiednia właściwość istnieje, false
inaczej.
in
rozwiązuje problem z wstawianiem fałszywych elementów 0
i false
. Teraz dodanie tych elementów na początku i na końcu daje oczekiwany wynik
.
Wskazówka 4: Destrukturyzacja w celu uzyskania dostępu do właściwości obiektu
Podczas uzyskiwania dostępu do właściwości obiektu czasami konieczne jest ustawienie wartości domyślnej, jeśli właściwość nie istnieje.
Aby to osiągnąć, możesz użyć in
wraz z operatorem trójskładnikowym:
Składnia operatora trójskładnikowego staje się zniechęcająca, gdy wzrasta liczba właściwości do sprawdzenia. Dla każdej właściwości musisz utworzyć nowy wiersz kodu obsługujący wartości domyślne, zwiększając brzydką ścianę podobnie wyglądających operatorów trójskładnikowych.
Aby zastosować bardziej eleganckie podejście, zapoznajmy się ze świetną funkcją ES2015 zwaną destrukcją obiektów.
Destrukturyzacja obiektów umożliwia wyodrębnianie wartości właściwości obiektu bezpośrednio do zmiennych i ustawienie wartości domyślnej, jeśli właściwość nie istnieje. Wygodna składnia pozwalająca uniknąć bezpośredniego zajmowania się undefined
.
Rzeczywiście, wyodrębnianie właściwości jest teraz precyzyjne:
Aby zobaczyć rzeczy w akcji, zdefiniujmy użyteczną funkcję, która zawija ciąg w cudzysłów.
quote(subject, config)
akceptuje pierwszy argument jako łańcuch do zawijania. Drugi argument config
to obiekt z właściwościami:
Stosując zalety destrukturyzacji obiektów, zaimplementujmy quote()
:
const { char = """, skipIfQuoted = true } = config
rozkładanie przypisania w jednym wierszu wyodrębnia właściwości char
i skipIfQuoted
z obiektu config
.
Jeśli w obiekcie config
brakuje niektórych właściwości, przypisanie destrukturyzacji ustawia wartości domyślne : """
for char
i false
dla skipIfQuoted
.
Na szczęście ta funkcja wciąż wymaga ulepszeń.
Przenieśmy przypisanie destrukturyzujące do sekcji parametrów. I ustaw wartość domyślną (pusty obiekt { }
) dla parametru config
, aby pominąć drugi argument, gdy ustawienia domyślne są wystarczające.
Przypisanie destrukturyzujące zastępuje parametr config
w sygnaturze funkcji. Podoba mi się: quote()
skraca się o jedną linię.
= {}
po prawej stronie przypisania destrukturyzującego zapewnia, że zostanie użyty pusty obiekt, jeśli drugi argument nie zostanie w ogóle określony quote("Sunny day")
.
Destrukturyzacja obiektów to potężna funkcja, która wydajnie obsługuje wyodrębnianie właściwości z obiektów. Podoba mi się możliwość określenia wartości domyślnej, która ma zostać zwrócona, gdy dostępna właściwość nie istnieje. W rezultacie unikniesz undefined
i kłopotów z nim związanych.
Wskazówka 5: Wypełnij obiekt właściwościami domyślnymi
Jeśli nie ma potrzeby tworzenia zmiennych dla każdej właściwości, jak to ma miejsce w przypadku przypisania destrukturyzującego, obiekt, który pomija niektóre właściwości, może zostać wypełniony z wartościami domyślnymi.
ES2015 Object.assign(target, source1, source2, ...)
kopiuje wartości wszystkich wyliczalnych własnych właściwości z jednego lub więcej obiektów źródłowych do obiektu docelowego. Funkcja zwraca obiekt docelowy.
Na przykład musisz uzyskać dostęp do właściwości obiektu unsafeOptions
, który nie zawsze zawiera pełny zestaw właściwości.
Aby uniknąć undefined
podczas uzyskiwania dostępu do nieistniejącej właściwości z unsafeOptions
, wprowadźmy pewne poprawki:
- Zdefiniuj obiekt
defaults
, który przechowuje domyślne wartości właściwości - Wywołaj
Object.assign({ }, defaults, unsafeOptions)
, aby zbudować nowy obiektoptions
. Nowy obiekt otrzymuje wszystkie właściwości zunsafeOptions
, ale brakujące są pobierane zdefaults
.
unsafeOptions
zawiera tylko właściwość fontSize
. Obiekt defaults
definiuje domyślne wartości właściwości fontSize
i color
.
Object.assign()
przyjmuje pierwszy argument jako obiekt docelowy {}
. Obiekt docelowy otrzymuje wartość właściwości fontSize
z obiektu źródłowego unsafeOptions
. A wartość właściwości color
z obiektu źródłowego defaults
, ponieważ unsafeOptions
nie zawiera color
.
Kolejność, w jakiej wyliczane są obiekty źródłowe, ma znaczenie: późniejsze właściwości obiektu źródłowego nadpisują wcześniejsze.
Możesz teraz bezpiecznie uzyskać dostęp do dowolnej właściwości obiektu options
, w tym options.color
, która nie była dostępna w unsafeOptions
początkowo.
Na szczęście istnieje łatwiejsza alternatywa wypełnienia obiektu właściwościami domyślnymi.Zalecam użycie właściwości rozłożenia w inicjatorach obiektów.
Zamiast wywołania Object.assign()
, użyj składni rozszerzania obiektów, aby skopiować do obiektu docelowego wszystkie własne i wyliczalne właściwości z obiektów źródłowych:
inicjator obiektu rozprzestrzenia właściwości z obiektów źródłowych defaults
i unsafeOptions
. Kolejność określania obiektów źródłowych jest ważna: późniejsze właściwości obiektu źródłowego zastępują wcześniejsze.
Wypełnianie niekompletnego obiektu domyślnymi wartościami właściwości to skuteczna strategia zapewniająca bezpieczeństwo i trwałość kodu. Niezależnie od sytuacji obiekt zawsze zawiera pełny zestaw właściwości: i nie można wygenerować undefined
.
Dodatkowa wskazówka: łączenie zerowe
Operator łączenie zerowe zwraca wartość domyślną, gdy jego operandem jest undefined
lub null
:
Zerowy operator łączenia jest wygodny w dostępie do właściwości obiektu, mając wartość domyślną, gdy ta właściwość to undefined
lub null
:
styles
obiekt nie ma właściwości color
, więc styles.color
akcesor właściwości to undefined
. styles.color ?? "black"
zwraca wartość domyślną "black"
.
styles.fontSize
to 18
, więc zerowy operator łączenia zwraca wartość właściwości 18
.
2.3 Parametry funkcji
Parametry funkcji domyślnie mają wartość
undefined
.
Zwykle funkcja zdefiniowana z określoną liczbą parametrów powinna być wywoływana z taką samą liczbą argumentów. Wtedy parametry uzyskują oczekiwane wartości:
Kiedy multiply(5, 3)
, parametry a
i b
otrzymują 5
i odpowiednio 3
wartości. Mnożenie jest obliczane zgodnie z oczekiwaniami: 5 * 3 = 15
.
Co się dzieje, gdy pominiesz argument przy wywołaniu? Odpowiedni parametr wewnątrz funkcji to undefined
.
Zmodyfikujmy nieco poprzedni przykład, wywołując funkcję z jednym argumentem:
Wywołanie multiply(5)
jest wykonywane z jednym argumentem: w rezultacie parametr a
to 5
, ale Parametr b
to undefined
.
Porada 6: Użyj domyślnej wartości parametru
Czasami funkcja nie wymaga pełnego zestawu argumentów przy wywołaniu. Możesz ustawić wartości domyślne dla parametrów, które nie mają wartości.
Przypominając poprzedni przykład, dokonajmy ulepszeń. Jeśli parametr b
to undefined
, ustaw go domyślnie na 2
:
Funkcja jest wywoływana z jednym argumentem multiply(5)
. Początkowo parametr a
to 2
, a b
to undefined
.
Instrukcja warunkowa sprawdza, czy b
to undefined
. Jeśli tak się stanie, przypisanie b = 2
ustawia wartość domyślną.
Chociaż przedstawiony sposób przypisywania wartości domyślnych działa, nie polecam bezpośredniego porównywania z undefined
. Jest rozwlekły i wygląda jak włamanie.
Lepszym podejściem jest użycie funkcji parametrów domyślnych ES2015. Jest krótkie, wyraziste i nie ma bezpośrednich porównań z undefined
.
Dodanie wartości domyślnej do parametru b = 2
wygląda lepiej:
b = 2
w sygnaturze funkcji zapewnia, że jeśli b
to undefined
, parametr domyślnie wynosi 2
.
Domyślne parametry ES2015 są intuicyjne i wyraziste. Zawsze używaj go do ustawiania wartości domyślnych parametrów opcjonalnych.
2.4 Wartość zwracana przez funkcję
Niejawnie, bez instrukcji
return
, funkcja JavaScript zwracaundefined
.
Funkcja, która nie ma return
niejawnie zwraca undefined
:
nie zwraca żadnych wyników obliczeń. Wynik wywołania funkcji to undefined
.
Taka sama sytuacja ma miejsce, gdy występuje instrukcja return
, ale w pobliżu nie ma wyrażenia:
return;
jest wykonywana, ale nie zwraca żadnego wyrażenia. Wynik wywołania to również undefined
.
Oczywiście wskazanie w pobliżu return
zwracanego wyrażenia działa zgodnie z oczekiwaniami:
Teraz wywołanie funkcji jest oceniane jako 4
, czyli 2
do kwadratu.
Wskazówka 7: Nie ufaj automatycznemu wstawianiu średnika
Poniższa lista instrukcji w JavaScript musi kończyć się średnikami (;
) :
- pusta instrukcja
-
let
,const
,var
,import
,export
deklaracje - wyrażenie wyrażenia
-
debugger
instrukcja -
continue
instrukcja,break
instrukcja -
throw
oświadczenie -
return
instrukcja
Jeśli używasz jednego z powyższych stwierdzeń, pamiętaj, aby na końcu umieścić średnik:
Na końcu obu let
deklaracja i return
zapis obowiązkowy średnik.
Co się stanie, jeśli nie chcesz ich podawać średniki? W takiej sytuacji ECMAScript zapewnia mechanizm automatycznego wstawiania średników (ASI), który wstawia brakujące średniki.
Z pomocą ASI możesz usunąć średniki z poprzedniego przykładu:
Powyższy tekst to prawidłowy kod JavaScript. Brakujące średniki są wstawiane automatycznie.
Na pierwszy rzut oka wygląda to całkiem obiecująco. Mechanizm ASI pozwala na pominięcie niepotrzebnych średników. Możesz zmniejszyć rozmiar kodu JavaScript i ułatwić jego czytanie.
Jest jedna mała, ale irytująca pułapka stworzona przez ASI. Gdy nowa linia znajduje się między return
a zwróconym wyrażeniem return \n expression
, ASI automatycznie wstawia średnik przed nową linią return; \n expression
.
Co to znaczy mieć instrukcję return;
wewnątrz funkcji? Funkcja zwraca undefined
. Jeśli nie znasz szczegółowo mechanizmu ASI, nieoczekiwanie zwrócony undefined
jest mylący.
Na przykład przestudiujmy zwróconą wartość wywołania getPrimeNumbers()
:
Pomiędzy instrukcją return
a wyrażeniem literału tablicowego istnieje nowa linia. JavaScript automatycznie wstawia średnik po return
, interpretując kod w następujący sposób:
Instrukcja return;
sprawia, że funkcja getPrimeNumbers()
zwraca undefined
zamiast oczekiwanej tablicy.
Problem został rozwiązany poprzez usunięcie nowej linii między return
a literałem tablicy:
Zalecam zbadanie, jak dokładnie działa automatyczne wstawianie średnika, aby uniknąć takich sytuacji.
Oczywiście nigdy nie umieszczaj nowej linii między return
a zwróconym wyrażeniem.
2,5 operator void
void <expression>
ocenia wyrażenie i zwraca undefined
bez względu na wynik oceny.
Jednym z przypadków użycia operatora void
jest pominięcie oceny wyrażenia do undefined
, opierając się na jakimś efekcie ubocznym oceny.
3. undefined w tablicach
Otrzymujesz undefined
podczas uzyskiwania dostępu do elementu tablicy z indeksem spoza granic.
colors
tablica ma 3 elementy, więc prawidłowe indeksy to 0
, 1
i 2
.
Ponieważ w indeksach 5
i -1
nie ma elementów tablicy, akcesory colors
i colors
to undefined
.
W JavaScript możesz napotkać tak zwane tablice rzadkie. Tezy to tablice, które mają luki, tj. W niektórych indeksach nie ma zdefiniowanych elementów.
Kiedy w rzadkiej tablicy uzyskuje się dostęp do luki (czyli pustego miejsca), otrzymujemy również undefined
.
Poniższy przykład generuje rzadkie tablice i próbuje uzyskać dostęp do ich pustych gniazd:
sparse1
jest tworzony przez wywołanie Array
z pierwszym argumentem numerycznym.Posiada 3 puste miejsca.
sparse2
jest tworzony za pomocą literału tablicowego z brakującym drugim elementem.
W każdej z tych rzadkich tablic dostęp do pustego gniazda daje undefined
.
Podczas pracy z tablicami, aby uniknąć undefined
, upewnij się, że używasz prawidłowych indeksów tablic i zapobiegaj tworzeniu rzadkich tablic.
4. Różnica między wartościami undefined i null
Jaka jest główna różnica między undefined
a null
? Obie wartości specjalne implikują stan pusty.
undefined
reprezentuje wartość zmiennej, która nie została jeszcze zainicjowana, natomiastnull
reprezentuje celową nieobecność obiektu.
Zbadajmy różnicę w kilku przykładach.
Zmienna number
jest zdefiniowana jednak nie ma przypisanej wartości początkowej:
number
zmienna to undefined
, co oznacza niezainicjowaną zmienną.
Ta sama niezainicjowana koncepcja ma miejsce, gdy uzyskuje się dostęp do nieistniejącej właściwości obiektu:
Ponieważ lastName
właściwość nie istnieje w obj
, JavaScript ocenia obj.lastName
na undefined
.
Z drugiej strony wiesz, że zmienna oczekuje obiektu. Ale z jakiegoś powodu nie możesz utworzyć wystąpienia obiektu. W takim przypadku null
jest znaczącym wskaźnikiem brakującego obiektu.
Na przykład clone()
to funkcja, która klonuje zwykły obiekt JavaScript. Funkcja powinna zwrócić obiekt:
Jednak clone()
może zostać wywołane z argumentem niebędącym przedmiotem: 15
lub null
. W takim przypadku funkcja nie może utworzyć klonu, więc zwraca null
– wskaźnik brakującego obiektu.
typeof
operator rozróżnia undefined
i null
:
Również operator ścisłej jakości ===
poprawnie rozróżnia undefined
from null
:
5. Podsumowanie
undefined
istnienie jest konsekwencją permisywnego charakteru JavaScript, który pozwala na użycie:
- niezainicjowanych zmiennych
- nieistniejące właściwości lub metody obiektu
- indeksy poza granicami dostępu do elementów tablicy
- wynik wywołania funkcji, która nic nie zwraca
Bezpośrednie porównywanie z undefined
jest niebezpieczne, ponieważ polegasz na dozwolonej, ale odradzanej praktyce wspomnianej powyżej.
Skuteczną strategią jest ograniczenie co najmniej występowania w kodzie słowa kluczowego undefined
poprzez stosowanie dobrych nawyków, takich jak:
- zmniejszyć użycie niezainicjowanych zmiennych
- skrócić cykl życia zmiennych i zbliżyć je do źródła ich użycia
- w miarę możliwości przypisać zmiennym wartości początkowe
- faworyzować
const
, w przeciwnym razie użyjlet
- użyj wartości domyślnych dla nieistotnych parametrów funkcji
- sprawdź właściwości istnienie lub wypełnienie niebezpiecznych obiektów domyślnymi właściwościami
- unikaj stosowania rzadkich tablic
Czy to dobrze, że JavaScript ma zarówno undefined
i null
do reprezentowania pustych wartości?