Cześć, w tym wpisie zajmę się omówieniem dwóch istotnych zagadnień, a mianowicie instrukcjami warunkowymi i pętlami(są to instrukcje blokowe). Pełnią one ważną rolę, gdyż umożliwiają kontrolę zachowania naszej aplikacji. Oznacza to, że możemy zmieniać działanie naszej aplikacji w zależności np.: od decyzji podjętych przez użytkownika.

Jak stworzyć instrukcję blokową?

Instrukcję blokową w JS definiuje się za pomocą dwóch nawiasów klamrowych.

{
    var x = 'Insrukcja blokowa';
    console.log(x);
}

Jednak instrukcje blokowe w takiej postaci rzadko występują. Często są łączone z takimi wyrażeniami jak if, for, while, switch. Są to tzw. control flow statements, których zadaniem jest sterowanie działaniem aplikacji.

Jak można wykorzystać te instrukcje pokażę na przykładzie prostej aplikacji, której zadaniem będzie stworzenie listy HTML, składającej się z odpowiedniej ilości elementów, w zależności od tego, ile będzie chciał użytkownik.

 

Na początku stwórzmy trzy pliki:

  • index.html
  • style.css
  • app.js

Plik index.html zawiera prosty kod HTML odpowiedzialny za wyświetlenie pola input, dzięki któremu użytkownik będzie mógł wprowadzić dane. Natomiast element div.output będzie zawierał gotową listę.

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Instrukcje Blokowe</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    
    <div class='list-creator'>
        <label for='count'>
            Ilu elementowa ma być lista?(1 - 20)
        </label>
        <input name='count' id='count' type='text'/>
        <input type='submit' id='submitBtn' value='Stwórz listę'>
    </div>

    <div class='output'>
        
    </div>

    <script src='app.js'></script>
</body>
</html>

Plik style.css za upiększenie strony.

* {
    font-family: Helvetica;
}

.list-creator {
    display: inline-block;
}

.list-creator * {
    margin-top: 1rem;
}

.list-creator label {
    display: block;
    width: 100%;
    padding: .3rem;
    font-size: 1.3rem;
}

.list-creator #count {
    width: 100%;
    font-size: 1.2rem;
    padding: .4rem;
}

.list-creator #submitBtn {
    padding: .4rem;
    background: #eee;
    outline: none;
    border: none;
    cursor: pointer;
}

.list-creator #submitBtn:active {
    background: #ddd;
}

ol {
    width: 250px;
    text-align: center;
    padding: 0;
    list-style-position: inside;
    list-style-type: none;
}

ol li {
    background-color: #eee;
    padding: .6rem;
    font-size: 1.3rem;
}

ol li:nth-child(odd) {
    background-color: #ddd;
}

Plik app.js zawiera logikę naszej aplikacji.

// app.js
(function () {
    // podstawowe zmienne
    var count,
        submitBtn,
        output;
    
    function handleCreateList() {
        // zmienna będzie przechowywać ilość elementów
        var howMany = count.value;
        // tworzona jest lista uporządkowana
        var list = document.createElement('ol');
        
        var i;
        var listItem;
        for(i = 1; i <= howMany; i++) {
            // tworzony jest element listy
            listItem = document.createElement('li');
            // ustawiana jest zawartość elementu listy
            listItem.innerHTML = 'Element numer ' + i;
            // element listy jest dołączany do końca listy
            list.appendChild(listItem);
        }
        // usuwamy zmienne, które nie będą potrzebne
        i        = null;
        listItem = null;
        
        // usuwamy poprzedną listę, jeśli by istniała
        output.innerHTML = null;
        // dodajemy nową listę do elementu div.output
        output.appendChild(list);
    }
    
    // odpowiada za pobranie elementów DOM, z pliku index.html
    function cacheDOM() {
        count           = document.querySelector('#count');
        submitBtn       = document.querySelector('#submitBtn');
        output          = document.querySelector('.output');
    }
    
    // odpowiada za dzialanie przyciku submit
    function bindEvents() {
        submitBtn.addEventListener('click', handleCreateList);
    }
    
    // odpowiada za start aplikacji
    function init() {
        cacheDOM();
        bindEvents();
    }
    
    // uruchamia aplikację
    init();
})();

Najpierw zacznę od wyjaśnienia wyrażenia typu

(function(){
  // nasz kod
})();

Jest to tzw. IIFE(ang. Immediately-Invoked Function Expression) i odnosi się do funkcji, która od razu się wywołuje. Umożliwia to od razu uruchomienie naszej aplikacji, a dzięki temu, że funkcje tworzą nowy zasięg(ang. scope), żadna zmienna nie będzie dostępna na zewnątrz funkcji.
Na początku definiujemy trzy zmienne, które będą przechowywać odniesienie do odpowiednich elementów DOM

  • count – będzie przechowywać element input#count
  • submitBtn – będzie przechowywać przycisk submit
  • output – będzie przechowywać element div.output, gdzie umieszczona zostanie nowa lista

Funkja cacheDOM() odpowiada właśnie za pobranie elementów DOM do wyżej wspomnianych zmiennych. W tym celu używa funkcji document.querySelector(selektorCSS), który pobiera elementy DOM w oparciu o selektory CSS.

Następnie funkcja bindEvents() odpowiada za przypisanie konkretnego działania(funkcji) do konkretnego zdarzenia(w tym przypadku kliknięcia) dla danego elementu. Korzysta z funkcji element.addEventListener(zdarzenie, handler), gdzie

  • element to element typu DOM(pobrany np.: za pomocą funkcji querySelector())
  • zdarzenie np.: kliknięcie(click), przyciśnięcie klawisza(keypress)
  • handler to funkcja odpowiadająca za to, co ma się stać po wystąpieniu konkretnego zdarzenia

Funkcja init() odpowiada za uruchomienie aplikacji.

Nasza aplikacja prezentuje się następująco

blok

Pętla for

Teraz czas wrócić do instrukcji blokowych. Jak widzimy w funkcji handleCreateList() znajduje się pętla for, która ma następującą postać

for(initialExpression; condition; incrementExpression) {
  // statement
}           
  • Initial expression zawiera wyrażenie, jakie ma być wykonane, tylko raz, na samym początku. Można tutaj deklarować zmienne, które będą użyte w pętli
  • condition jest to warunek, który musi zostać spełniony, aby rozpoczęło się kolejne wykonanie pętli
  • incrementExpression jest to wyrażenie, które jest wykonywane po każdym wykonaniu pętli

W naszym przypadku kod pętli for jest następujący

var i;
var listItem;
for(i = 1; i <= howMany; i++) {
    // tworzony jest element listy
    listItem = document.createElement('li');
    // ustawiana jest zawartość elementu listy
    listItem.innerHTML = 'Element numer ' + i;
    // element listy jest dołączany do końca listy
    list.appendChild(listItem);
}
// usuwamy zmienne, które nie będą potrzebne
i = null;
listItem = null;
  1. Wyrażenie i = 1, ustala początkową wartość zmiennej i.
  2. Następnie sprawdzany jest warunek czy zmienna i jest mniejsza lub równa zmiennej howMany(przechowuje ona liczbę określającą z ilu elementów ma się składać lista).
  3. Po każdej iteracji, zmienna i będzie zwiększana o 1, dzięki czemu pętla nie będzie działać w nieskończoność.

Ponadto pętla for może występować jeszcze w dwóch innych formach, których zadaniem jest skrócenie zapisu i łatwiejsze działanie na obiektach czy tablicach.

  • wersja z operatorem in iteruje po indeksach w tablicy lub po właściwościach obiektu
// tworzona jest tablica
var arr = ['Warszawa', 'Kraków', 'Londyn', 'Barcelona'];

for(var i in arr) {
    console.log(i);
}
// wyświetli w konsoli przeglądarki 0 1 2 3
// tworzony jest obiekt
var obj = {
    prop1: 'wartość 1',
    prop2: 'wartość 2',
    prop3: 'wartość 3'
}

for(var i in obj) {
    console.log(i);
}
// wyświetli w konsoli przeglądarki prop1 prop2 prop3
  • wersja z operatorem of iteruje natomiast po wartościach elementów w tablicy
// tworzona jest tablica
var arr = ['Warszawa', 'Kraków', 'Londyn', 'Barcelona'];

for(var i of arr) {
    console.log(i);
}
//wyświetli w konsoli przeglądarki Warszawa Kraków Londyn Barcelona

 

Pętle while i do…while

Oprócz pętli for, istnieją inne pętle. Jedną z nich jest pętla while. Ma ona prostszą postać niż pętla for, jednak używając tej pętli można uzyskać taki sam efekt, jak przy użyciu pętli for.

var a = 5;
var b = 3;
while(a > b) {
    console.log('a jest większe od b');
    b++;
}
//dwa razy zostanie wyświetlony napis

Na początku pętli while sprawdzany jest warunek, jeśli jest spełniony to wykonywane są instrukcje zawarte między nawiasami klamrowymi.

Pętla do…while jest bardzo podobna do pętli while.

var a = 5;
var b = 3;
do {
    console.log('a jest większe od b');
    b++;
}while(a > b);
//dwa razy zostanie wyświetlony napis

Te dwie pętle różnią małym subtelnym detalem. Pętla do…while wykona się na pewno jeden raz, natomiast pętla while niekoniecznie. Spójrzmy na następujący przykład:

  • z użyciem pętli do…while napis wyświetli się raz
var a = 5;
var b = 3;
do {
    console.log('a jest większe od b');
    b++;
}while(a < b);
//jeden raz zostanie wyświetlony napis
  • z użyciem pętli while napis się w ogóle nie wyświetli
var a = 5;
var b = 3;
while(a < b) {
    console.log('a jest większe od b');
    b++;
}
// napis się nie wyświetlił

Dzieje się tak dlatego, że pętla while na samym początku sprawdza czy warunek został spełniony, natomiast pętla do…while zaczyna sprawdzać warunek dopiero po pierwszym wykonaniu funkcji.

Instrukcja if/else

Zadaniem instrukcji if/else jest sprawdzenie czy dany warunek został spełniony. Tylko tyle.

var a = 5;
var b = 3;

if(a > b) {
    console.log('a jest większe od b');
}
//wyświetli napis

Aby jednak instrukcje się nie wykonały, warunek musi zwrócić jedną z tzw. falsy values. 

Są to wartości takie jak:

  • 0
  • „” (pusty napis)
  • null
  • undefined
  • false
  • NaN (Not a Number)
if(null) {
    console.log('instrukcja if');
}
//napis się nie wyświetli

Warunki, jakie muszą zostać spełnione, mogą być proste, tak jak w przykładzie powyżej. Można je jednak ze sobą łączyć, co pozwala na dokładniejsze sprecyzowanie, kiedy można wykonać instrukcje w bloku.

var a = 5;
var b = 3;

if(a > b && a + b === 8) {
    console.log('instrukcja się wykona');
}
//wyświetli napis

Jak widzimy, w przykładzie został użyty operator &&(and), który wymaga, aby oba warunki zostały spełnione. Jeśli chociaż jedno wyrażenie zwróci wartość false(warunek się nie spełnił), to instrukcje wewnątrz bloku if się nie wykonają.

Oprócz && istnieje też operator ||(or), który wymaga, aby chociaż jeden warunek się spełnił.

var a = 5;
var b = 3;

if(a < b || a + b === 8) {
    console.log('suma jest równa 8');
}
//wyświetli napis

Istnieje jeszcze operator !(negacji), który zmienia wartość na przeciwną(z true na false i odwrotnie)

if(!false) {
    console.log('instrukcja wykona się);
}

 

Pozostaje jeszcze jedno pytanie. Jak zareagować w przypadku, gdy warunek nie zostanie spełniony? Z pomocą przychodzi instrukcja else i else if. 

Spójrzmy na przykład

var a = 5;
var b = 3;

if(a < b) {
    console.log('instrukcja ta nie wykona się');
}
else {
    console.log('ta instrukcja się wykona');
}
//wyświetli się napis z drugiego bloku

Można zauważyć, że gdy pierwszy warunek nie zostanie spełniony, to zostanie wykony blok else.

Natomiast w przykładzie poniżej została użyta instrukcja else if.

var a = 5;
var b = 3;

if(a < b) {
    console.log('instrukcja ta nie wykona się');
}
else if(a > b) {
    console.log('ta instrukcja się wykona');
}
else {
    console.log('ta instrukcja się nie wykona');
}
//wyświetli się napis z drugiego bloku

A więc po kolei.

  1. Na początku sprawdzany jest pierwszy warunek, który nie jest prawdziwy. Instrukcja wewnątrz bloku się nie wykona.
  2. Dalej sprawdzany jest blok else if, warunek jest zgodny z prawdą i instrukcja w bloku się wykona.
  3. Blok else by się wykonał tylko wtedy, gdyby każdy z wcześniejszych bloków if/else if nie spełniłby warunków.

Instrukcja switch

Ta instrukcja jest bardzo przydatna w sytuacji, gdy chcemy podjąć decyzję w zależności od konkretnego przypadku, wartości zmiennej. Taki sam efekt może dać też zastosowanie instrukcji if/else, jednak switch pozwala to zrobić w bardziej elegancki sposób.

var x = 1;

switch(x) {
    case 0:
        console.log('Nie wykona się');
        break; 
    case 1:
        console.log('Wykona się');
        break;
    case 2:
        console.log('Nie wykona się');
        break;
    default:
        console.log('Nie wykona się');
}

Jak widzimy w zależności od zmiennej x, możemy podjąć decyzję, co robić dalej. Należy pamiętać, aby nie zapomnieć wstawić słówka break, ponieważ gdy się go nie wstawi, to instrukcje będą wykonywane dalej.

var x = 1;

switch(x) {
    case 0:
        console.log('Nie wykona się');
        break; 
    case 1:
        console.log('Wykona się'); // brak słówka break
    case 2:
        console.log('To też się wykona się'); 
        break;
    default:
        console.log('Nie wykona się');
}

Mimo tego, że warunek został spełniony tylko dla jedynki, to z powodu słówka break zostanie także wykonana instrukcja dla przypadku z dwójką.

Natomiast słówko default zachowuje się podobnie jak instrukcja else tzn. zostanie wykonana tylko wtedy, gdy żadny pozostały warunek nie zostanie spełniony.

var x = 7;

switch(x) {
    case 0:
        console.log('Nie wykona się');
        break; 
    case 1:
        console.log('Nie wykona się');
        break;
    case 2:
        console.log('Nie wykona się');
        break;
    default:
        console.log('Wykona się');
}

Słówka kluczowe break i continue

Czasem może zdarzyć się sytuacja, w której nie chcemy, aby wszystkie iteracje pętli się wykonały. Wtedy z pomocą przychodzą nam dwie instrukcje

  • break – przerywa wykonywanie funkcji
  • continue – przechodzi bezpośrodnie do kolejnej iteracji pętli

Spójrzmy na przykład z break

for(var i = 0; i < 5; i++) {
    if(i === 2)
        break;
    console.log(i);
}
// => 0 1

Gdy zmienna i osiągnie wartość 2, pętla zostaje przerwana i nie wykonują się iteracje dl 2, 3 i 4. Natomiast inaczej ma się sytuacja, gdy zamiast break, użyjemy continue.

for(var i = 0; i < 5; i++) {
    if(i === 2)
        continue;
    console.log(i);
}
// => 0 1 3 4 

Iteracje pętli zostają wykonane dla wszystkich wartości, oprócz dwójki.

Nie zawsze musimy używać { }

Czasami nasze nasze bloki składają się tylko z jednej instrukcji. Wtedy nie musimy używać nawiasów klamrowych.

var x = 5;
if(x > 3)
    console.log('x jest większe od 3');

for(var i = 0; i < 3; i++)
    console.log(i);

Ulepszenie aplikacji

Uzbrojeni w dodatkową wiedzę, rozszerzmy naszą aplikację. Pozwala nam ona tworzyć listę składającą się z kilku elementów. Jednak my chcemy, aby mógł tworzyć listę maksymalnie 20-elementową. Ponadto chcemy poinformować użytkownika, aby wpisywał dodatnie cyfry, gdy będzie wpisywał litery lub liczby ujemne.

Do dzieła!

Dodatkowy kod będzie się znajdować tutaj

// app.js
function handleCreateList() {
    // zmienna będzie przechowywać ilość elementów
    var howMany = count.value;
        
    //tutaj będzie nasz nowy kod   


    // tworzona jest lista uporządkowana
    var list = document.createElement('ol');
    // pozostały kod funkcji   
    
}

Na początku sprawdzimy czy nasza zmienna howMany(przechowująca wartość pola input) nie jest liczbą lub czy nie jest liczbą ujemną. Nie chcemy, aby użytkownik stworył listę składającą się z -8 lub 4$#0 elementów :).

// sprawdzamy czy zmienna howMany nie jest liczbą lub czy jest liczbą ujemną
if(isNaN(howMany) || howMany < 1) {
    // wyświetlamy komunikat
    alert('Oops, chyba nie podałeś prawidłowej liczby. Spróbuj jeszcze raz.');
    // zerujemy zawarość pola input
    count.value = null;
    // przerywamy wykonanie funkcji
    return;
}

Jak widzimy, użyta została instrukcja if. Funkcja isNan(zmienna) sprawdza czy zmienna jest liczbą. W przeciwnym wypadku zwraca wartość false.

Gdy choć jeden z przypadków został spełniony, wyświetlany jest komunikat za pomocą standardowej funkcji alert(treść_komunikatu).

Instrukcja return powoduje natychmiastowe przerwanie funkcji, podobnie jak break przerywa pętle. Jednak więcej o instrukcji return napiszę w poście poświęconym funkcjom.

Wystarczy jeszcze sprawdzić, czy zmienna howMany nie jest większa od 20. W tym celu skorzystamy z instrukcji else if.

if(isNaN(howMany) || howMany < 1) {
    // instrukcje
}
// sprawdzamy czy zmienna howMany jest mniejsza od 20
else if(howMany > 20) {
    // wyświetlamy komunikat
    alert('Oops, lista nie może być aż tak długa. Spróbuj jeszcze raz.');
    // przerywamy wykonanie funkcji
    count.value = null;
    // przerywamy wykonanie funkcji
    return;
}

Postępujemy podobnie jak wcześniej, tylko zmieniamy postać warunki w instrukcji. Możemy zauważyć, że część kodu się powtarza(chodzi tu o wyświetlenie komunikatu i wyzerowanie  wartości pola input). Nie jest to zgodne z zasadą DRY(Don’t Repeat Yourself).

Nie byłbym sobą, gdybyśmy to tak zostawili. Trzeba stworzyć nową funkcję, która będzie przyjmować jak parametr treść komunikatu.

//app.js

// parametr msg - to treść komunikatu
function showInfo(msg) {
    // wyświetlamy komunikat
    alert(msg);
    // zerujemy zawarość pola input
    count.value = null;
}

function handleCreateList() { }

Tak wygląda nasza funkcja. Teraz musimy zmienić nasz kod w instrukcji if/else.

if(isNaN(howMany) || howMany < 1) {
    // wywołujemy funkcję
    showInfo('Oops, chyba nie podałeś prawidłowej liczby. Spróbuj jeszcze raz.');
    // przerywamy wykonanie funkcji
    return;
}
else if(howMany > 20) {
    // wywołujemy funkcję
    showInfo('Oops, lista nie może być aż tak długa. Spróbuj jeszcze raz.');
    // przerywamy wykonanie funkcji
    return;
}

Teraz gdy wpiszemy niepoprawną wartość, zostaniemy o tym poinformowani.

blok2.png

Podsumowanie

W tym wpisie pokazałem jak można wykorzystać pętle i instrukcje warunkowe do budowy prostej aplikacji. Dają one dużo możliwości do kontrolowania przepływu działania naszej aplikacji.

Mam nadzieję, że wpis się podobał. Zapraszam do komentowania. Chętnie odpowiem na wszystkie komentarze.

Narazie.