Zimní výprodej Zimní výprodej
40 % bodů zdarma díky naší Zimní akci!

Lekce 14 - Cykly v JavaScriptu podruhé

V minulé lekci, Podmínky v JavaScriptu podruhé, jsme si prohloubili naše znalosti o datových typech a ukázali si další konstrukce pro tvorbu podmínek. V dnešní lekci rozšíříme naše znalosti cyklů v JavaScriptu. Naučíme se používat cyklus do...while, příkazy break a continue, návěstí a nakonec si ukážeme možnosti zkráceného zápisu for cyklu.

Na úvod by bylo dobré podotknout, že dnešní lekce obsahuje méně používané praktiky a slouží hlavně k tomu, aby vás nepřekvapily v cizím kódu. Není příliš důležité, abyste je sami uměli používat.

do...while

Cyklus while již dobře známe. Méně používaný do...while se liší pouze tím, že se vždy 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)

Příklad

Použití do-while si ukažme 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 2 stran v trojúhelníku je vždy delší než třetí strana, 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. Pomocí cyklu do-while to uděláme následovně:

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

Varianta s cyklem while

Pro lepší srovnání si ukažme i jak by kód vypadal s námi již známým 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.`);

Ukázka v prohlížeči:

Cyklus while
index.html

Všimněte si, že jsme se u varianty s while museli zamyslet nad výchozí hodnotou proměnných, které jsme všechny nastavili na 0, aby podmínka nebyla ihned splněná. V případě trojúhelníku by se podmínka nesplnila ani pro hodnoty undefined, tedy kdybychom proměnné vůbec neinicializovali, je ovšem přehlednější hodnoty uvést. Vymyslet výchozí hodnotu může být někdy poměrně složité a i vyžadovat pomocnou proměnnou.

break a continue

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

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.

Příklad

Představme si, že máme seznam položek a chceme v nich nějakou najít. Že můžeme použít metodu indexOf()? Pokud půjde o pole, tak ano, ale některé kolekce ji nemají a/nebo chceme hledat pomocí nějaké jiné vlastnosti, než kterou indexOf() zohledňuje. Pak si vyhledávání musíme napsat hezky 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. Zatím stále nic nového pod sluncem. V tu samou chvíli ovšem i pomocí break cyklus ukončíme.

Ukázka použití break:

<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, viz dále. Proto doporučuji break nepoužívat.

Příklad pomocí return
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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 a ještě bude univerzální a s vyhledaným elementem půjde udělat cokoli, nejen ho odstranit.

break tedy svádí spíše k psaní dlouhých "nudlí" neuniverzálního kódu a neměli bychom jej používat. Přednosti varianty s return můžete vidět na upraveném příkladu níže:

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

Your page
localhost

continue

Příkaz continue je podobný 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ř. při validování položek při procházení nějaké kolekce.

Příklad

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 každé číslo je oddělené čárkou. Bohužel musíme počítat i s tím, že uživatel zadá místo čísla nějaký nesmysl nebo 0. Ř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);
    if (Number.isNaN(celeCislo)) continue; // hodnoty NaN ignorujeme
    soucet += celeCislo;
}
document.write('Součet je: ' + soucet + '.');

Ukázka v prohlížeči:

Cyklus s continue
index.html

Skript sečte všechna správně zadaná čísla. Hodnoty NaN, které parseInt() vrací v případě, že se parsování nepovedlo, se 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 (příklad viz použití else namísto return; níže).

Návěstí (label)

Pomocí návěstí si můžeme v JavaScriptu pojmenovat cyklus. Toho můžeme využít např. pokud budeme chtít při použití vnořených cyklů ukončit uvnitř vnitřního cyklu cyklus vnější.

Syntaxe návěstí je poměrně zmatečná a v praxi je lze opět elegantně obejít pomocí return za předpokladu, že je kód ve funkci. Doporučuji je spíše nepoužívat.

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 seznam (pole) 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 např. 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ší a tak ukončíme 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
index.html

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.

Můžete si příklad vyzkoušet i bez návěstí a také bez příkazu break.

Návěstí se příliš často nepoužívá a je možné se bez něj obejít, zde ho uvádíme spíše pro úplnost.

Příklad s return

Lepším řešením než použít návěstí je opět kód vložit do funkce a tu při nalezení zaměstnance ukončit přes 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 s totožným výstupem bude s funkcí a return vypadat 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:

Your page
localhost

return;

Když jsme již zavítali k return, řekněme si, že je dokonce možné zavolat return 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 (Number.isNan(a) || Number.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 (Number.isNan(a) || Number.isNan(b))
        alert('Zadejte celá čísla!');
    else {
        alert(a + b);
        // ...
    }
};

Pozor! Pokud funkce vrací nějakou hodnotu, nikdy v ní nevolejte jen 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
index.html

Psát logiku průběhu běhu cyklu i logiku v cyklu na jeden řádek ovšem není intuitivní. Navíc se tak může snadno zapomenout na inkrementaci proměnné nebo ji inkrementovat vícekrát. 

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

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

Tento zápis je stejný jako:

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

Pozor: 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ůžete 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 ale není přímo v deklaraci cyklu, je poměrně nepřehledné zjistit kdy cyklus vůbec skončí a snadné v 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 příští lekci, Obrázky a kreslení na canvas v JavaScriptu, se budeme věnovat práci s obrázky.


 

 

Aktivity (8)

 

 

Komentáře

Avatar
Vladislav Ladicky:17. února 22:27

Label sa nielen že často nepoužíva, ale sa vyslovene nemá používať. Pri zapnutom strict móde tuším ani len nefunguje.

Tie skrátené cykly by sa ani nemali spomínať. Je to zmätočné a takmer nikde to už neprejde cez code review.

Naopak, miesto nich by sa malo spomenúť / zdôrazniť, prečo vo for cykle pre definíciu pomocnej premennej použiť let a nie var. (where applicable)

A ako posledné, v článku nie sú uvedené cykly for of, for in, a ani for await of.

 
Odpovědět
17. února 22:27
Avatar
Roman
Redaktor
Avatar
Odpovídá na Vladislav Ladicky
Roman:18. února 12:31

Label sa nielen že často nepoužíva, ale sa vyslovene nemá používať. Pri zapnutom strict móde tuším ani len nefunguje.

Label funguje i při stric módu.

Tie skrátené cykly by sa ani nemali spomínať. Je to zmätočné a takmer nikde to už neprejde cez code review.

Souhlas, v článku se zkrácený for nedoporučuje.

Naopak, miesto nich by sa malo spomenúť / zdôrazniť, prečo vo for cykle pre definíciu pomocnej premennej použiť let a nie var. (where applicable)

Dobrý tip, ale bylo by asi na celý další clanek var vs let...

A ako posledné, v článku nie sú uvedené cykly for of, for in, a ani for await of.

For of je součástí článku pole a na for await of je zde v tomto seriálu příliš brzy.

Díky za komentář.

 
Odpovědět
18. února 12:31
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Vladislav Ladicky
David Čápka:18. února 12:52

Však je tam napsané, že se to nemá používat. Kdyby tu ale takový článek nebyl, někdo by to mohl objevit jinde a začít používat, proto tu je.

Odpovědět
18. února 12:52
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 3 zpráv z 3.