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
ab + c > a
.
Strany budeme náhodně generovat tak dlouho, dokud nebudou splněny výše uvedené podmínky:
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:
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:
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ý:
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:
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:
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:
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:
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