Lekce 22 - 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
ab + 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:
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:
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:
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ý:
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:
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:
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:
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:
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 15.-22. 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 41x (3.52 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript