23. diel - Cykly v JavaScripte tretíkrát
V minulej lekcii, Podmienky v JavaScripte tretíkrát, sme si ukázali ďalšie konštrukcie na tvorbu podmienok.
V dnešnom tutoriále základov JavaScriptu rozšírime
naše znalosti cyklov. Naučíme sa používať cyklus
do
- while
, príkazy break
a
continue
a povieme si, čo je návestiou. Nakoniec si ukážeme
možnosti skráteného zápisu for
cyklu.
Cyklus do
- while
Cyklus while
už dobre poznáme. Najprv testuje
podmienku a pokiaľ je od začiatku nepravdivá, telo
cyklu sa nikdy nespustí. Keď tento typ cyklu v kóde
použijeme, je možné, že nebude vykonaný ani raz. Oproti tomu sa cyklus
do
- while
vykoná najmenej raz. Jeho
podmienka je totiž umiestnená až za telom cyklu. Vyzerá
teda takto:
do { // code... } while (condition)
Náhodný trojuholník
Použitie do
- while
si ukážeme na príklade.
Vygenerujeme trojuholník s náhodnou dĺžkou strán. Dĺžku strany
vygenerujeme ako celé číslo v intervale <1,10>
. Aby ale
trojuholník šiel nakresliť, musí platiť veta o stranách:
Súčet ľubovoľných dvoch strán v trojuholníku je vždy väčší ako dĺžka tretej strany. Musí teda platiťStrany budeme náhodne generovať tak dlho, pokiaľ nebudú splnené vyššie uvedené podmienky:a + b > c
,a + c > b
ab + c > a
.
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(`Triangle: a = ${a} cm, b = ${b} cm, c = ${c} cm.`);
Ukážka v prehliadači:
Variant s cyklom while
Pre lepšie porovnanie si ukážme, ako by kód vyzeral s cyklom
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(`Triangle: a = ${a} cm, b = ${b} cm, c = ${c} cm.`);
Všimnime si, že v uvedených príkladoch je podmienka v zátvorke
(a + b <= c || a + c <= b || b + c <= a)
. Táto podmienka
zaistí, že cyklus pokračuje, kým nie sú nájdené hodnoty
a
, b
, c
, ktoré možno použiť na
vytvorenie platného trojuholníka. Podmienka je teda v skutočnosti negáciou
citovanej vety o stranách trojuholníka.
Pri variante s cyklom while
sme sa museli ešte
zamyslieť nad východiskovou hodnotou premenných, ktoré sme všetky nastavili
na 0
, aby sa cyklus spustil. V našom prípade by sa cyklus spustil
aj s hodnotami undefined
, teda keby sme premenné vôbec
neinicializovali, je však prehľadnejšie hodnoty uviesť.
Ukážka v prehliadači:
Nasledujúca časť lekcie obsahuje menej používané praktiky. Slúžia hlavne na to, aby nás tieto praktiky neprekvapili v cudzom kóde. Nie je teraz príliš dôležité, aby sme ich sami vedeli používať.
Príkazy break
a
continue
Beh cyklu je potrebné niekedy prerušiť, k tomu máme nasledujúce dve kľúčové slová.
Príkaz break
Príkaz break
ukončuje aktuálny cyklus.
Používa sa najčastejšie, pokiaľ pomocou cyklu nájdeme nejakú
položku v kolekcii a ďalej už v jej prechádzaní nechceme
pokračovať. Nebudeme tak ďalej zbytočne prehľadávať zvyšok kolekcie,
keď už máme to, čo sme hľadali.
Pokiaľ pracujeme s poľom, môžeme samozrejme použiť metódu
indexOf()
, ale niektoré kolekcie ju nemajú alebo chceme hľadať
pomocou nejakej inej vlastnosti. Potom si vyhľadávanie musíme napísať
ručne cyklom alebo použiť výrazne pokročilejšie konštrukcie, než teraz
ovládame.
Majme teda HTML zoznam <ul>
av ňom elementy
<li>
s nejakými textami. Budeme chcieť zmazať element
obsahujúci text Cucumbers
. Metódou
getElementById()
vyberieme zoznam a potom začneme cyklom
prechádzať jednotlivé položky <li>
. Akonáhle nájdeme
požadovanú položku, element odstránime, zároveň pomocou break
cyklus ukončíme:
<ul id ="fruit-list"> <li>Apples</li> <li>Pears</li> <li>Cucumbers</li> <li>Plums</li> </ul> <script> let fruitList = document.getElementById('fruit-list'); for (let fruit of fruitList.childNodes) { if (fruit.textContent === 'Cucumbers') { fruitList.removeChild(fruit); break; } } </script>
Ukážka v prehliadači:
Príkaz break
sa v praxi skôr nahrádza príkazom
return
za predpokladu, že je kód vo funkcii. Príkaz
break
teda zvádza skôr k písaniu dlhých "slíži"
neuniverzálneho kódu a nemali by sme ho používať.
Riešenie s príkazom
return
Už sme si hovorili, že kód by sme mali členiť do funkcií. Správne by
sme teda mali pre vyhľadávanie vytvoriť funkciu. V tej potom môžeme na
ukončenie cyklu použiť return
. Kód potom bude univerzálny a s
vyhľadaným elementom pôjde urobiť čokoľvek, nielen ho odstrániť.
Prednosti varianty s return
môžeme vidieť na upravenom
príklade:
<ul id ="fruit-list"> <li>Apples</li> <li>Pears</li> <li>Cucumbers</li> <li>Plums</li> </ul> <script> function findItem(element, text) { for (let subelement of element.childNodes) if (subelement.textContent === text) return subelement; } let fruitList = document.getElementById('fruit-list'); fruitList.removeChild(findItem(fruitList, 'Cucumbers')); </script>
Výsledok v prehliadači je rovnaký:
Príkaz continue
Príkaz continue
je podobný príkazu break
.
Používa sa však na ukončenie iba aktuálnej iterácie
(priebehu) cyklu a nie celého cyklu. Cyklus potom rovno
prechádza na ďalšiu iteráciu. Použitie continue
môžeme
nájsť napríklad pri validovaní položiek počas prehliadania nejakej
kolekcie.
Predstavme si, že máme od užívateľa zadané čísla a tieto čísla chceme sčítať. Užívateľ tieto čísla zadá ako jeden reťazec, kde je každé číslo oddelené čiarkou. Bohužiaľ musíme počítať aj s tým, že používateľ zadá namiesto čísla nejaký nezmysel. Riešenie by mohlo vyzerať nasledovne:
let commaSeparatedNumbers = '10,50,abcd,30,9'; // parsing a string into an array let numbers = commaSeparatedNumbers.split(','); let total = 0; for (let number of numbers) { // converting a string to an integer let integer = parseInt(number); // returns NaN for non-numeric input if (isNaN(integer)) continue; // we ignore NaN values total += integer; } document.write('Total is: ' + total + '.');
Operátor +=
slúži na zjednodušenie zápisu,
keď chceme k existujúcej hodnote premennej pripočítať nejakú ďalšiu
hodnotu. Zápis a += b
je teda ekvivalentom zápisu
a = a + b
.
Ukážka v prehliadači:
Skript spočíta všetky správne zadané čísla. Pre nečíselné vstupy
vráti funkcia parseInt()
hodnotu NaN
, tie sa potom
vďaka podmienke preskočia. Namiesto continue
by sme samozrejme
mohli použiť len blok else
, kód by sme tým však zbytočne
zanorili.
Návestie (label)
Pomocou návestia si môžeme v JavaScripte pomenovať cyklus. Návestie potom využijeme pri vnorených cykloch, keď chceme ukončiť vo vnútri vnútorného cyklu vonkajší cyklus.
Syntax návestia je pomerne zmätočná. Opäť je možné
elegantne obísť pomocou return
za predpokladu, že je kód vo
funkcii. Riešenie s return
budeme teda uprednostňovať.
Príklad
Ukážme si, ako návestia vyzerajú. Predpokladajme, že máme nejakú databázu zamestnancov a budeme chcieť nájsť zamestnancov zodpovedných za dané oddelenie. Našu databázu tu bude predstavovať pole polí. Každý zamestnanec bude mať meno a vo vnorenom poli zoznam oddelení, za ktoré je zodpovedný:
let employees = [ [ 'John Smith', [ 'pastry', 'drinks' ] ], [ 'Charles Wilson', [ 'electro', 'household items' ] ], [ 'Ava Brown', [ 'frozen products', 'dairy products' ] ] ];
Ak budeme hľadať zamestnancov zodpovedného za oddelenie
elektro
, musíme použiť vnorené cykly. Najprv prejdeme
zamestnancov a potom ich oddelenie. Po nájdení hľadaného zamestnanca už nie
je nutné prechádzať ďalšie. Ukončíme teda vonkajší cyklus z cyklu
vnútorného.
Ukážka použitia návestia:
employeeLoop: for (let employee of employees) { for (let department of employee[1]) { document.write('Repeating the internal cycle.<br>'); if (department === 'electro') { document.write('The electrical department is in charge of ' + employee[0] + '.<br>'); break employeeLoop; } } }
Ukážka v prehliadači:
Ukážka by fungovala aj bez použitia návestií, došlo by tu ale k
nadbytočným prechodom cyklom, pretože by samotný break
ukončil
len vnútorný cyklus. Dokonca je možné aj príkaz break
úplne
vynechať, ale to by znamenalo ďalšie opakovanie cyklu navyše.
Riešenie s príkazom
return
Lepším riešením je opäť kód vložiť do funkcie. Tú pri nájdení
zamestnanca ukončíme pomocou return
. Rovnako budeme časom v
našom programe potrebovať nájsť zamestnancov pravdepodobne aj na inom mieste
a predsa nebudeme kopírovať znova ten istý kód. Vložíme ho do funkcie a
tú zavoláme všade, kde potrebujeme zamestnancov vyhľadávať.
Príklad upravíme nasledovne:
let employees = [ [ 'John Smith', [ 'pastry', 'drinks' ] ], [ 'Charles Wilson', [ 'electro', 'household items' ] ], [ 'Ava Brown', [ 'frozen products', 'dairy products' ] ] ]; function findEmployeeByDepartment(searchedDepartment) { for (let employee of employees) { for (let department of employee[1]) { document.write('Repeating the internal cycle<br>'); if (department === searchedDepartment) { return employee[0]; } } } } let searchedEmployee = findEmployeeByDepartment('electro'); document.write('The electrical department is in charge of ' + searchedEmployee + '<br>');
Výsledok:
Ďalšie využitie príkazu
return
Príkaz return
je možné volať aj v prípade, že
funkcia nevracia žiadnu hodnotu. Funkciu tak
ukončíme. Použitie je podobné, ako pri validácii pomocou
continue
, kedy si ušetríme blok s podmienkou a kód je potom
prehľadnejší.
Ukážme si príklad obsluhy kliknutí na tlačidlo kalkulačky z minulých lekcií a doplňme si kontrolu, či sú čísla zadané:
button.onclick = function() { let a = parseFloat(number1.value); let b = parseFloat(number2.value); if (isNaN(a) || isNaN(b)) { alert('Enter integer!'); return; } alert(a + b); // ... };
Bez znalosti tejto praktiky by sme museli dať zvyšok programu do bloku
else
a odsadiť hlavnú časť metódy do ďalšieho bloku. Ak by
kód podmienok obsahoval viac a nie len na začiatku, mohol by byť veľmi
odsadený a neprehľadný:
button.onclick = function() { let a = parseFloat(number1.value); let b = parseFloat(number2.value); if (isNaN(a) || isNaN(b)) { alert('Enter integer!'); else { alert(a + b); // ... } };
Pokiaľ funkcia vracia nejakú hodnotu, nikdy v nej
nevolejme samotné return
. Funkcia by buď mala
vždy niečo vracať, alebo nevracať nikdy nič. Použitie funkcie, ktorá
vracia hodnotu len niekedy, je potenciálny zdroj chýb
v programe.
Skrátený zápis cyklu for
Nasledujúce konštrukcie sú tu pre ukážku, čo všetko je možné stretnúť v cudzích kódoch a nie je dobrý dôvod ich používať!
Cyklus for
je možné zapísať takto skrátene, bez tela
cyklu:
for (let i = 0; i < 10; document.write(i++));
Ukážka v prehliadači:
Zapisovať ako priebeh cyklu, tak aj logiku vo vnútri cyklu na jeden riadok nie je príliš intuitívne. Navyše sa tak môže ľahko zabudnúť na inkrementáciu premennej alebo ju budeme omylom inkrementovať viackrát.
V hlavičke cyklu for
dokonca nie je nutné uvádzať
akýkoľvek príkaz:
for (;;) { // endless loop }
Tento zápis je rovnaký ako:
while (true) { // endless loop }
Oba príklady povedú k zaseknutiu vlákna danej záložky
prehliadača! Oba vyššie deklarované cykly bežia do nekonečna a môžeme
ich stretnúť v zle napísaných zdrojových kódoch spolu s príkazmi
break
, ktoré z nich potom za nejakých podmienok vyskakujú.
Akonáhle podmienka nie je priamo v deklarácii cyklu, je pomerne neprehľadné zistiť, kedy cyklus vôbec skončí. Je potom ľahké urobiť z takého cyklu nechtiac nekonečný, zvlášť, keď z neho vyskakujeme viacerými podmienkami a nepokryjeme všetky možné prípady.
V nasledujúcom cvičení, Riešené úlohy k 15.-23. lekciu JavaScriptu, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 3x (3.42 kB)
Aplikácia je vrátane zdrojových kódov v jazyku JavaScript