Instrukcje warunkowe w Javascript

Instrukcje warunkowe umożliwiają wykonanie różnych bloków kodu w zależności od spełnienia określonych warunków. Innymi słowy, pozwalają programowi podjąć decyzję: jeśli dany warunek jest prawdziwy, wykonaj pewne czynności, a jeśli nie – wykonaj inne (lub pomiń je). Dzięki temu program może reagować na różne sytuacje i dane wejściowe w sposób dynamiczny. Podstawową instrukcją warunkową w JavaScript (jak i w wielu innych językach) jest instrukcja if.

Instrukcja if (jeżeli)

Instrukcja if sprawdza wybrany warunek logiczny i jeśli warunek ten zwróci true (prawdę), wykonywany jest blok kodu znajdujący się wewnątrz instrukcji. Jeżeli warunek jest false (fałsz), kod ten zostanie pominięty. Ogólna postać (składnia) instrukcji if jest następująca:

 if (warunek) {
    // kod do wykonania, gdy warunek jest prawdziwy (true)
 } 
  • Słowo kluczowe if rozpoczyna instrukcję warunkową.
  • W nawiasach okrągłych () umieszczamy warunek logiczny – jest to wyrażenie, które zostanie obliczone do wartości true lub false (często używamy tutaj operatorów porównania i logicznych, omówionych wcześniej).
  • Jeśli warunek jest spełniony (true), wykonany zostanie kod zawarty wewnątrz nawiasów klamrowych { ... }.
  • Jeśli warunek jest niespełniony (false), kod wewnątrz klamr zostanie pominięty (przeskoczony).

Rozważmy prosty przykład: chcemy sprawdzić, czy liczba w zmiennej x jest dodatnia. Jeżeli tak, wypiszemy komunikat w konsoli.

 let x = 5;
 if (x > 0) {
     console.log("x jest liczbą dodatnią");
 } 

W powyższym kodzie warunek to x > 0. Dla x równego 5 warunek ten jest prawdziwy, więc wewnątrz bloku if zostanie wykonana instrukcja console.log(...) i w konsoli zobaczymy napis „x jest liczbą dodatnią”. Gdyby zmienna x miała wartość ujemną lub zero, warunek x > 0 byłby fałszywy i tym samym ciało instrukcji if zostałoby pominięte (w tym przypadku program nic by nie wypisał).

Ważna uwaga: Jeżeli kod do wykonania po if składa się tylko z jednej instrukcji, często w JavaScript spotyka się pominięcie nawiasów klamrowych (pisząc np. if (warunek) instrukcja;). Jednakże zaleca się zawsze używać { ... } dla czytelności i unikania błędów, szczególnie w kodzie początkujących – ułatwia to rozbudowę warunku o kolejne instrukcje.

Blok if...else (jeżeli… w przeciwnym razie)

Sama instrukcja if pozwala wykonać kod, gdy warunek jest spełniony. Często jednak chcemy również zdefiniować, co powinno się stać, gdy warunek nie jest spełniony. Służy do tego rozszerzenie else. Konstrukcja if...else wygląda tak:

 if (warunek) {    // kod gdy warunek jest true
 } else {
    // kod gdy warunek jest false
 } 

Działanie jest następujące: jeśli warunek jest prawdą, wykona się pierwszy blok (po if), natomiast jeżeli warunek okaże się fałszem – zostanie wykonany alternatywny blok po słowie kluczowym else. Zawsze dokładnie jeden z tych dwóch bloków zostanie wykonany (nie ma możliwości, by wykonały się oba, albo by pominąć oba – jedna z gałęzi zostanie wybrana w zależności od warunku).

Przykład: Sprawdźmy, czy liczba w zmiennej x jest parzysta czy nieparzysta i poinformujmy o tym:

 let x = 7;
 if (x % 2 == 0) {
     console.log(x + " jest liczbą parzystą");
 } else {
     console.log(x + " jest liczbą nieparzystą");
 }

W tym kodzie warunkiem jest x % 2 == 0, czyli „czy reszta z dzielenia x przez 2 wynosi 0″. To standardowy sposób sprawdzenia parzystości liczby – liczby parzyste dają resztę 0 przy dzieleniu przez 2. Dla x = 7 warunek ten jest fałszywy (7 % 2 to 1, nie 0), więc zostanie wykonany blok w części else. Program wypisze: „7 jest liczbą nieparzystą”. Gdyby x wynosiło np. 8, warunek x % 2 == 0 byłby prawdziwy i wykonany zostałby pierwszy blok, wypisując „8 jest liczbą parzystą”.

Wielokrotne warunki: if...else if...else

Często zdarza się, że potrzebujemy sprawdzić więcej niż jeden warunek i w zależności od tego, który z nich jest spełniony, wykonać różne czynności. Możemy łączyć wiele instrukcji warunkowych, wykorzystując konstrukcję else if. Jej składnia to po prostu dodatkowy if po początkowym if, poprzedzony słowem else:

 if (warunek1) {
    // kod gdy warunek1 jest true
 } else if (warunek2) {
    // kod gdy warunek1 jest false, a warunek2 jest true
 } else if (warunek3) {
    // kod gdy warunek1 i warunek2 są false, a warunek3 jest true
 } else {
    // kod gdy żaden z powyższych warunków nie jest spełniony
 } 

Możemy tu mieć dowolną liczbę sekcji else if (nawet zero lub wiele), a na końcu opcjonalny blok else na wypadek, gdy żaden z wcześniejszych warunków nie będzie prawdziwy. Program przechodzi od góry na dół przez kolejne warunki:

  • Jeśli warunek1 jest spełniony, wykona jego blok i pominie resztę (pozostałe warunki nie są już sprawdzane).
  • Jeśli warunek1 jest fałszywy, za to warunek2 jest prawdziwy – wykonuje blok else if i kończy całą konstrukcję.
  • Jeśli pierwsze dwa warunki są fałszywe, a warunek3 jest prawdziwy – wykona się trzecia gałąź.
  • Jeśli żaden z warunków 1, 2, 3 nie był prawdą, wykona się na końcu blok else (o ile został podany).

Przykład: Mamy zmienną temp z bieżącą temperaturą (w stopniach Celsjusza) i chcemy skategoryzować pogodę na podstawie tej temperatury:

 let temp = 18; if (temp >= 30) {
     console.log("Bardzo gorąco");
 } else if (temp >= 20) {
     console.log("Ciepło");
 } else if (temp >= 10) {
     console.log("Chłodno");
 } else {
     console.log("Zimno");
 } 

W tym kodzie sprawdzamy kolejno:

  • Czy temp >= 30 (np. 30°C lub więcej to bardzo gorąco). Jeśli tak, wypisujemy odpowiedni komunikat i pomijamy resztę.
  • Jeśli pierwszy warunek nie był spełniony, sprawdzamy temp >= 20 (20–29°C uznajemy za ciepło).
  • Jeśli to również było fałszem, sprawdzamy temp >= 10 (10–19°C traktujemy jako chłodno).
  • Jeśli żadna z powyższych granic nie została osiągnięta (czyli mamy poniżej 10°C), w bloku else dajemy komunikat „Zimno”.

Dla temp = 18 spełniony jest dopiero trzeci warunek (temp >= 10), więc na konsoli zobaczymy napis „Chłodno”. Gdyby temp wynosiło 32, już pierwszy warunek byłby spełniony i program od razu wypisałby „Bardzo gorąco”, nie sprawdzając reszty. Konstrukcja if...else if...else jest czytelnym sposobem obsługi wielu wzajemnie wykluczających się warunków.

Zagnieżdżone instrukcje warunkowe

Warto wspomnieć, że instrukcje warunkowe można zagłębiać (zagnieżdżać) wewnątrz siebie. Oznacza to, że wewnątrz bloku if lub else możemy umieścić kolejny if. Takie podejście czasem bywa potrzebne, choć należy stosować je ostrożnie, by kod nie stał się zbyt skomplikowany w czytaniu. Alternatywą bywa często użycie operatorów logicznych, aby połączyć warunki w jednym if (zamiast dwóch osobnych zagnieżdżonych). Dla przykładu spójrzmy na dwa równoważne fragmenty kodu:

 // Wariant zagnieżdżony
 let username = "Admin";
 let password = "1234";
 if (username == "Admin") {
     if (password == "1234") {
         console.log("Logowanie powiodło się");
     }
 }

 // Wariant z łączeniem warunków logicznych
 if (username == "Admin" && password == "1234") {
     console.log("Logowanie powiodło się");
 } 

Oba przykłady sprawdzają, czy jednocześnie username jest równe „Admin” i password jest równe „1234”. Pierwszy robi to poprzez zagnieżdżenie (drugi if wewnątrz pierwszego), drugi zaś wykorzystuje operator logiczny && w jednym warunku. Rezultat działania obu fragmentów jest taki sam – w konsoli pojawi się komunikat o poprawnym logowaniu tylko jeśli oba warunki są spełnione. Widać, że druga wersja jest bardziej zwięzła i czytelna, dlatego warto łączyć warunki logiczne zamiast nadmiernie zagnieżdżać if, tam gdzie to możliwe.

Instrukcja switch

Instrukcja switch to alternatywny sposób obsługi warunkowego sterowania programem, szczególnie przydatny, gdy mamy wiele możliwych wartości jednej zmiennej lub wyrażenia i dla każdej z tych wartości chcemy wykonać inny kod. Składnia instrukcji switch jest nieco odmienna od if, opiera się na pojęciu przypadków (ang. cases):

 switch (wyrażenie) {
     case wartość1:
         // kod do wykonania, gdy wyrażenie == wartość1
         break;
     case wartość2:
         // kod dla przypadku, gdy wyrażenie == wartość2
         break;
        // ... (kolejne przypadki case w razie potrzeby)
     default:
         // kod do wykonania, gdy wyrażenie nie pasuje do żadnego powyższego przypadku
         break;
 }

Działanie switch jest następujące:

  • Najpierw obliczane jest podane wyrażenie (w nawiasie po słowie switch). Często jest to po prostu zmienna, którą chcemy sprawdzić.
  • Następnie wartość tego wyrażenia jest porównywana kolejno z każdą z podanych po case wartości:
    • Jeśli zostanie znaleziony odpowiadający przypadek (case), wykonywany jest kod następujący po dwukropku :.
    • Instrukcja break na końcu każdego przypadku powoduje wyjście ze struktury switch po wykonaniu tego jednego wybranego przypadku. Bez break program kontynuowałby sprawdzanie kolejnych przypadków (co zwykle nie jest pożądane – z wyjątkiem zaawansowanych zastosowań).
  • Jeśli żadna z wartości case nie pasuje do wyniku wyrażenia, wykonywany jest blok oznaczony jako default (domyślny). Blok default jest opcjonalny, ale często się go stosuje, by obsłużyć sytuacje nieprzewidziane innymi przypadkami.
  • Po wykonaniu dopasowanego bloku (lub default) następuje break, które kończy całą instrukcję switch (ew. zakończenie bloku switch jeśli to ostatnia część).

Przykład: załóżmy, że mamy zmienną day z numerem dnia tygodnia (np. 0 = niedziela, 1 = poniedziałek, …, 6 = sobota) i chcemy przypisać do zmiennej nazwanaDnia polską nazwę tego dnia tygodnia. Możemy użyć switch:

 let day = 3;  // załóżmy, że 0-niedziela, 1-poniedziałek, ..., 6-sobota
 let nazwaDnia;
 switch(day) {
     case 0:
         nazwaDnia = "Niedziela";
         break;
     case 1:
         nazwaDnia = "Poniedziałek";
         break;
     case 2:
         nazwaDnia = "Wtorek";
         break;
     case 3:
         nazwaDnia = "Środa";
         break;
     case 4:
         nazwaDnia = "Czwartek";
         break;
     case 5:
         nazwaDnia = "Piątek";
         break;
     case 6:
         nazwaDnia = "Sobota";
         break;
     default:
         nazwaDnia = "Nieznany dzień";
         break;
 }
 console.log("Dzień " + day + " to " + nazwaDnia);

Dla day = 3 powyższy kod znajdzie przypadek case 3, przypisze zmiennej nazwaDnia wartość „Środa” i wykona break, które kończy switch. Zmienna nazwaDnia będzie więc zawierać „Środa”, a ostatnia linia wypisze: „Dzień 3 to Środa”. Gdyby zmienna day miała wartość spoza zakresu 0–6 (np. 7 lub -1), żaden z przypadków nie pasowałby i wykonałby się blok default, ustawiając nazwaDnia na „Nieznany dzień”.

Jak widać, switch bywa czytelniejszy od rozbudowanego łańcucha if...else if... w sytuacji, gdy porównujemy tę samą zmienną do wielu różnych wartości. Należy pamiętać o umieszczaniu break po każdym bloku przypadku, aby uniknąć tzw. przechodzenia (fall-through), w którym kod „przelatuje” do kolejnego przypadku. Jeśli celowo chcemy zgrupować kilka wartości, które mają prowadzić do tego samego kodu, można dać kilka case pod rząd bez break, a dopiero na końcu wspólny blok kodu i jeden break. Przykładowo, gdybyśmy traktowali zarówno case 0 jak i 6 (niedziela i sobota) jako „weekend”, moglibyśmy napisać:

 switch(day) {
     case 0:
     case 6:
         console.log("Weekend!");
         break;
     // ... reszta przypadków ...
 }

W powyższym fragmencie, dla day = 0 lub day = 6, kod wejdzie w przypadek i ponieważ po case 0 nie ma break, od razu przejdzie dalej – natrafi na case 6, wykona jego blok (wypisze „Weekend!”) i wyjdzie z switch. Dla innych wartości day ten fragment zostanie pominięty.

Operator warunkowy ?: (ternary)

Oprócz instrukcji if i switch, w JavaScript istnieje jeszcze tzw. operator warunkowy (zwany też trójargumentowym, ternary operator), zapisywany symbolem ? :. Nie jest to struktura składniowa jak if, lecz operator, a zatem pozwala zbudować wyrażenie zwracające pewną wartość w zależności od warunku. Składnia operatora warunkowego jest następująca:

warunek ? wartość_gdy_true : wartość_gdy_false

Działanie: jeśli warunek jest prawdziwy, całe wyrażenie przyjmuje wartość po znaku ? (przed dwukropkiem), natomiast jeśli warunek jest fałszywy – wartość po dwukropku :. Operator ten wymaga trzech operandów: warunku, wyniku dla true i wyniku dla false – stąd nazwa ternary (trójargumentowy).

Przykład: przypuśćmy, że mamy zmienną liczba i chcemy przypisać zmiennej parzystosc tekst „parzysta” lub „nieparzysta” w zależności od tego, czy liczba jest parzysta. Zamiast używać if...else, możemy to zrobić w jednej linii z ?::

 let liczba = 10;
 let parzystosc = (liczba % 2 == 0) ? "parzysta" : "nieparzysta";
 console.log(parzystosc); // "parzysta"

Tutaj warunkiem jest liczba % 2 == 0. Jeśli jest on spełniony, parzystosc otrzyma wartość „parzysta”, w przeciwnym razie „nieparzysta”. Dla liczby 10 wynik warunku to true, więc zmienna parzystosc będzie miała wartość „parzysta”. Operator ?: bywa bardzo przydatny do prostych przypisań zależnych od warunku lub do wyrażenia czegoś krótkiego w jednej linijce. Należy jednak unikać zagnieżdżania wielu operatorów ?: lub umieszczania w nich zbyt skomplikowanego kodu, gdyż może to obniżyć czytelność. W takich wypadkach zwykły if...else często okazuje się bardziej przejrzysty.

Kontekst logiczny (truthy/falsy): Warto wspomnieć, że w JavaScript warunek w instrukcji if (albo w operatorze warunkowym) nie musi być bezpośrednio wyrażeniem true/false. Interpreter języka próbuje go zinterpretować jako wartość prawdziwą lub fałszywą zgodnie z pewnymi regułami. Na przykład, wartości takie jak 0, "" (pusty string), null, undefined oraz NaN są traktowane jako fałszywe (tzw. falsy values), natomiast inne wartości liczbowe (poza zerem), niepuste łańcuchy znaków czy obiekty są traktowane jako prawdziwe (truthy values). Przykładowo, if (5) {...} zostanie wykonane (bo 5 jest traktowane jak true), zaś if (0) {...} zostanie pominięte (bo 0 zachowuje się jak false). Mimo istnienia tych zasad, dla czytelności kodu lepiej jest explicite porównywać wartości w warunkach (np. if (x != 0) zamiast if (x) w kontekście liczbowym), aby nie polegać zbytnio na pamiętaniu, które wartości są truthy/falsy, szczególnie na początku nauki.

Instrukcje warunkowe odgrywają kluczową rolę w sterowaniu przepływem programu. Dzięki nim program podejmuje decyzje i może wykonywać różne operacje zależnie od danych. Gdy już opanujemy warunki, następnym krokiem jest możliwość wielokrotnego wykonywania pewnych fragmentów kodu – temu służą pętle, które omówimy w kolejnej sekcji.