NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Lekce 23 - 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:

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.`);

Délku strany vygenerujeme jako celé číslo v intervalu <1,10>. K tomu nám poslouží funkce random() z knihovny Math, která vrací náhodné desetinné číslo od nuly do jedné. Číslo vynásobíme deseti a zaokrouhlíme pomocí další funkce floor() ze stejné knihovny, čímž získáme celé číslo v požadovaném intervalu. 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:

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 obou uvedených příkladech je podmínka (a + b <= c || a + c <= b || b + c <= a) v závorce a zajišťuje, že cyklus pokračuje, dokud nejsou nalezeny hodnoty a, b, c, které lze použít k vytvoření platného trojúhelníku. Podmínka je tedy ve skutečnosti negací citovaného pravidla 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 nám slouží 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:

<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>

Když 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:

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:

<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 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.

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:

let cislaRetezec = '10,50,abcd,30,9';
let cislaPole = cislaRetezec.split(',');
let soucet = 0;
for (let cislo of cislaPole) {
    let celeCislo= parseInt(cislo);
    if (isNaN(celeCislo)) continue;
    soucet += celeCislo;
}
document.write('Součet je: ' + soucet + '.');

Uživatel tato čísla zadá jako jeden řetězec, kde je každé číslo oddělené čárkou. Metodou split() rozdělíme řetězec do pole. 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.

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.

Skript sečte všechna správně zadaná čísla:

Cyklus s continue
localhost

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í:

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' ] ]
];

Každý zaměstnanec bude mít jméno a ve vnořeném poli výčet oddělení, za která je zodpovědný. Pokud budeme hledat zaměstnance zodpovědného za oddělení elektro, musíme použít vnořené cykly:

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;
        }
    }
}

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:

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:

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>');

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řece 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:

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 lekce Základy práce s DOM a události v JavaScriptu a doplňme si kontrolu, zda jsou čísla zadaná:

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

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 cislo1 = parseFloat(cislo1Element.value);
    let cislo2 = parseFloat(cislo2Element.value);
    if (isNaN(a) || isNaN(b))
        alert('Zadejte celá čísla!');
    else {
        alert(cislo1 + cislo2);
    }
};

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 (;;) {
    ...
}

Tento zápis vytvoří nekonečný cyklus. Zápis odpovídá tomuto:

while (true) {
    ...
}

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 15.-23. 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 114x (3.47 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 15.-23. lekci JavaScriptu
Článek pro vás napsal Roman
Avatar
Uživatelské hodnocení:
947 hlasů
Roman
Aktivity