Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 21 - Cykly v JavaScriptu potřetí

V minulé lekci, Podmínky v JavaScriptu potřetí, jsme si ukázali další konstrukce pro tvorbu podmínek.

V dnešním tutoriálu základů JavaScriptu rozšíříme naše znalosti cyklů. Naučíme se používat cyklus do-while, příkazy break a continue a řekneme si, co je návěstí. Nakonec si ukážeme možnosti zkráceného zápisu for cyklu.

Cyklus do-while

Cyklus while již dobře známe. Nejprve testuje podmínku a pokud je od začátku nepravdivá, tělo cyklu se nikdy nespustí. Když tento typ cyklu v kódu použijeme, je možné, že nebude vykonán ani jednou. Oproti tomu se cyklus do-while vykoná nejméně jednou. Jeho podmínka je totiž umístěna až za tělem cyklu. Vypadá tedy takto:

do {
    // kód...
} while (podmínka)

Náhodný trojúhelník

Použití do-while si ukážeme na příkladu. Vygenerujeme trojúhelník o náhodné délce stran. Délku strany vygenerujeme jako celé číslo v intervalu <1,10>. Aby ale trojúhelník šel nakreslit, musí platit věta o stranách:

Součet libovolných dvou stran v trojúhelníku je vždy větší než délka třetí strany. Musí tedy platit a + b > c, a + c > b a b + c > a.

Strany budeme náhodně generovat tak dlouho, dokud nebudou splněny výše uvedené podmínky:

let a, b, c;
do {
    a = Math.floor(Math.random() * 10) + 1;
    b = Math.floor(Math.random() * 10) + 1;
    c = Math.floor(Math.random() * 10) + 1;
} while (a + b <= c || a + c <= b || b + c <= a);
document.write(`Trojúhelník: a = ${a} cm, b = ${b} cm, c = ${c} cm.`);

Ukázka v prohlížeči:

Cyklus do-while
localhost

Varianta s cyklem while

Pro lepší srovnání si ukažme, jak by kód vypadal s cyklem while:

let a = 0, b = 0, c = 0;
while (a + b <= c || a + c <= b || b + c <= a) {
    a = Math.floor(Math.random() * 10) + 1;
    b = Math.floor(Math.random() * 10) + 1;
    c = Math.floor(Math.random() * 10) + 1;
}
document.write(`Trojúhelník: a = ${a} cm, b = ${b} cm, c = ${c} cm.`);

Všimněme si, že v uvedených příkladech je podmínka v závorce (a + b <= c || a + c <= b || b + c <= a). Tato podmínka zajistí, že cyklus pokračuje, dokud nejsou nalezeny hodnoty a, b, c, které lze použít k vytvoření platného trojúhelníka. Podmínka je tedy ve skutečnosti negací citované věty o stranách trojúhelníku.

U varianty s cyklem while jsme se museli ještě zamyslet nad výchozí hodnotou proměnných, které jsme všechny nastavili na 0, aby se cyklus spustil. V našem případě by se cyklus spustil i s hodnotami undefined, tedy kdybychom proměnné vůbec neinicializovali, je ovšem přehlednější hodnoty uvést.

Ukázka v prohlížeči:

Cyklus while
index.html

Následující část lekce obsahuje méně používané praktiky. Slouží hlavně k tomu, aby nás tyto praktiky nepřekvapily v cizím kódu. Není nyní příliš důležité, abychom je sami uměli používat.

Příkazy break a continue

Běh cyklu je potřeba někdy přerušit, k tomu máme následující dvě klíčová slova.

Příkaz break

Příkaz break ukončuje aktuální cyklus. Používá se nejčastěji, pokud pomocí cyklu nalezneme nějakou položku v kolekci a dále již v jejím procházení nechceme pokračovat. Nebudeme tak dále zbytečně prohledávat zbytek kolekce, když již máme to, co jsme hledali.

Pokud pracujeme s polem, můžeme samozřejmě použít metodu indexOf(), ale některé kolekce ji nemají nebo chceme hledat pomocí nějaké jiné vlastnosti. Pak si vyhledávání musíme napsat ručně cyklem nebo použít výrazně pokročilejší konstrukce, než nyní ovládáme.

Mějme tedy HTML seznam <ul> a v něm elementy <li> s nějakými texty. Budeme chtít smazat element obsahující text Okurky. Metodou getElementById() vybereme seznam a poté začneme cyklem procházet jednotlivé položky <li>. Jakmile najdeme požadovanou položku, element odstraníme, zároveň pomocí break cyklus ukončíme:

<ul id ="seznam-ovoce">
  <li>Jablka</li>
  <li>Hrušky</li>
  <li>Okurky</li>
  <li>Švestky</li>
</ul>
<script>
let seznamOvoce = document.getElementById('seznam-ovoce');
for (let ovoce of seznamOvoce.childNodes) {
    if (ovoce.textContent === 'Okurky') {
        seznamOvoce.removeChild(ovoce);
        break;
    }
}
</script>

Ukázka v prohlížeči:

Cyklus s break
index.html

Příkaz break se v praxi spíše nahrazuje příkazem return za předpokladu, že je kód ve funkci. Příkaz break tedy svádí spíše k psaní dlouhých "nudlí" neuniverzálního kódu a neměli bychom jej používat.

Řešení s příkazem return

Již jsme si říkali, že kód bychom měli členit do funkcí. Správně bychom tedy měli pro vyhledávání vytvořit funkci. V té potom můžeme k ukončení cyklu použít return. Kód pak bude univerzální a s vyhledaným elementem půjde udělat cokoli, nejen ho odstranit.

Přednosti varianty s return můžeme vidět na upraveném příkladu:

<ul id ="seznam-ovoce">
  <li>Jablka</li>
  <li>Hrušky</li>
  <li>Okurky</li>
  <li>Švestky</li>
</ul>
<script>

function najdiPolozku(element, text) {
    for (let podelement of element.childNodes)
        if (podelement.textContent === text)
            return podelement;
}

let seznamOvoce = document.getElementById('seznam-ovoce');
seznamOvoce.removeChild(najdiPolozku(seznamOvoce, 'Okurky'));
</script>

Výsledek v prohlížeči je stejný:

Tvoje stránka
localhost

Příkaz continue

Příkaz continue je podobný příkazu break. Používá se však k ukončení pouze aktuální iterace (průběhu) cyklu a ne celého cyklu. Cyklus poté rovnou přechází na další iteraci. Použití continue můžeme najít například při validování položek během procházení nějaké kolekce.

Představme si, že máme od uživatele zadaná čísla a tato čísla chceme sečíst. Uživatel tato čísla zadá jako jeden řetězec, kde je každé číslo oddělené čárkou. Bohužel musíme počítat i s tím, že uživatel zadá místo čísla nějaký nesmysl. Řešení by mohlo vypadat následovně:

let cislaRetezec = '10,50,abcd,30,9';
// rozložení řetězce do pole
let cislaPole = cislaRetezec.split(',');
let soucet = 0;
for (let cislo of cislaPole) {
    // převedení řetězce na celé číslo
    let celeCislo= parseInt(cislo); // pro nečíselný vstup vrátí NaN
    if (isNaN(celeCislo)) continue; // hodnoty NaN ignorujeme
    soucet += celeCislo;
}
document.write('Součet je: ' + soucet + '.');

Operátor += slouží k zjednodušení zápisu, když chceme k existující hodnotě proměnné přičíst nějakou další hodnotu. Zápis a += b je tedy ekvivalentem zápisu a = a + b.

Ukázka v prohlížeči:

Cyklus s continue
localhost

Skript sečte všechna správně zadaná čísla. Pro nečíselné vstupy vrátí funkce parseInt() hodnotu NaN, ty se pak díky podmínce přeskočí. Místo continue bychom samozřejmě mohli použít jen blok else, kód bychom tím ovšem zbytečně zanořili.

Návěstí (label)

Pomocí návěstí si můžeme v JavaScriptu pojmenovat cyklus. Návěstí poté využijeme u vnořených cyklů, když chceme ukončit uvnitř vnitřního cyklu cyklus vnější.

Syntaxe návěstí je poměrně zmatečná. Opět lze elegantně obejít pomocí return za předpokladu, že je kód ve funkci. Řešení s return budeme tedy upřednostňovat.

Příklad

Ukažme si, jak návěstí vypadají. Předpokládejme, že máme nějakou databázi zaměstnanců a budeme chtít najít zaměstnance zodpovědného za dané oddělení. Naši databázi zde bude představovat pole polí. Každý zaměstnanec bude mít jméno a ve vnořeném poli výčet oddělení, za která je zodpovědný:

let zamestnanci = [
    [ 'Jan Novotný', [ 'pečivo', 'nápoje' ] ],
    [ 'Karel Starý', [ 'elektro', 'domácí potřeby' ] ],
    [ 'Pavla Nováková', [ 'mražené výrobky', 'mléčné výrobky' ] ]
];

Pokud budeme hledat zaměstnance zodpovědného za oddělení elektro, musíme použít vnořené cykly. Nejprve projdeme zaměstnance a poté jejich oddělení. Po nalezení hledaného zaměstnance již není nutné procházet další. Ukončíme tedy vnější cyklus z cyklu vnitřního.

Ukázka použití návěstí:

cyklusZamestnanci: for (let zamestnanec of zamestnanci) {
    for (let oddeleni of zamestnanec[1]) {
        document.write('Opakovaní vnitřního cyklu.<br>');
        if (oddeleni === 'elektro') {
            document.write('Oddělení elektro má na starosti ' + zamestnanec[0] + '.<br>');
            break cyklusZamestnanci;
        }
    }
}

Ukázka v prohlížeči:

Cyklus s návěstím a break
localhost

Ukázka by fungovala i bez použití návěstí, došlo by zde ale k nadbytečným průchodům cyklem, protože by samotný break ukončil jen vnitřní cyklus. Dokonce je možné i příkaz break úplně vynechat, ale to by znamenalo další opakování cyklu navíc.

Řešení s příkazem return

Lepším řešením je opět kód vložit do funkce. Tu při nalezení zaměstnance ukončíme pomocí return. Stejně budeme časem v našem programu potřebovat najít zaměstnance pravděpodobně i na jiném místě a přeci nebudeme kopírovat znovu ten samý kód. Vložíme jej do funkce a tu zavoláme všude, kde potřebujeme zaměstnance vyhledávat.

Příklad upravíme následovně:

let zamestnanci = [
    [ 'Jan Novotný', [ 'pečivo', 'nápoje' ] ],
    [ 'Karel Starý', [ 'elektro', 'domácí potřeby' ] ],
    [ 'Pavla Nováková', [ 'mražené výrobky', 'mléčné výrobky' ] ]
];

function najdiZamestnancePodleOddeleni(hledaneOddeleni) {
    for (let zamestnanec of zamestnanci) {
        for (let oddeleni of zamestnanec[1]) {
            document.write('Opakovaní vnitřního cyklu<br>');
            if (oddeleni === hledaneOddeleni) {
                return zamestnanec[0];
            }
        }
    }
}

let hledanyZamestnanec = najdiZamestnancePodleOddeleni('elektro');
document.write('Oddělení elektro má na starosti ' + hledanyZamestnanec + '<br>');

Výsledek:

Tvoje stránka
localhost

Další využití příkazu return

Příkaz return lze volat i v případě, že funkce nevrací žádnou hodnotu. Funkci tak ukončíme. Použití je podobné, jako při validaci pomocí continue, kdy si ušetříme blok s podmínkou a kód je potom přehlednější.

Ukažme si příklad obsluhy kliknutí na tlačítko kalkulačky z minulých lekcí a doplňme si kontrolu, zda jsou čísla zadaná:

tlacitko.onclick = function() {
    let a = parseInt(cislo1.value);
    let b = parseInt(cislo2.value);
    if (isNaN(a) || isNaN(b)) {
        alert('Zadejte celá čísla!');
        return;
    }
    alert(a + b);
    // ...
};

Bez znalosti této praktiky bychom museli dát zbytek programu do bloku else a odsadit hlavní část metody do dalšího bloku. Pokud by kód podmínek obsahoval více a ne jen na začátku, mohl by být hodně odsazený a nepřehledný:

tlacitko.onclick = function() {
    let a = parseInt(cislo1.value);
    let b = parseInt(cislo2.value);
    if (isNaN(a) || isNaN(b))
        alert('Zadejte celá čísla!');
    else {
        alert(a + b);
        // ...
    }
};

Pokud funkce vrací nějakou hodnotu, nikdy v ní nevolejme samotné return. Funkce by buď měla vždy něco vracet, nebo nevracet nikdy nic. Použití funkce, která vrací hodnotu jen někdy, je potenciální zdroj chyb v programu.

Zkrácený zápis cyklu for

Následující konstrukce jsou zde pro ukázku, co vše je možné potkat v cizích kódech a není dobrý důvod je používat!

Cyklus for je možné zapsat takto zkráceně, bez těla cyklu:

for (let i = 0; i < 10; document.write(i++));

Ukázka v prohlížeči:

Cyklus for
localhost

Zapisovat jak průběh cyklu, tak i logiku uvnitř cyklu na jeden řádek není příliš intuitivní. Navíc se tak může snadno zapomenout na inkrementaci proměnné nebo ji budeme omylem inkrementovat vícekrát. 

V hlavičce cyklu for dokonce není nutné uvádět jakýkoliv příkaz:

for (;;) {
    // nekonečný cyklus
}

Tento zápis je stejný jako:

while (true) {
    // nekonečný cyklus
}

Oba příklady povedou k zaseknutí vlákna dané záložky prohlížeče! Oba výše deklarované cykly běží do nekonečna a můžeme je potkat ve špatně napsaných zdrojových kódech spolu s příkazy break, které z nich potom za nějakých podmínek vyskakují.

Jakmile podmínka není přímo v deklaraci cyklu, je poměrně nepřehledné zjistit, kdy cyklus vůbec skončí. Je pak snadné udělat z takového cyklu nechtěně nekonečný, zvláště, když z něj vyskakujeme více podmínkami a nepokryjeme všechny možné případy.

V následujícím cvičení, Řešené úlohy k 14.-21. lekci JavaScriptu, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 33x (3.52 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

Předchozí článek
Podmínky v JavaScriptu potřetí
Všechny články v sekci
Základní konstrukce jazyka JavaScript
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 14.-21. lekci JavaScriptu
Článek pro vás napsal Roman
Avatar
Uživatelské hodnocení:
690 hlasů
Roman
Aktivity