6 min read

Feltételvizsgálatok és ciklusok

Feltételvizsgálatok és ciklusok

Újabb mérföldkőhöz érkeztünk a címben említett két témával. Valójában az egyik nélkül lehetetlen lenne programozni, a másik nélkül pedig meglehetősen nehézkes.

A feltételvizsgálat egy speciális formája bemutatásra került az operátoros cikkben, ám a feltételtől függő programvégrehajtáshoz nem a legcélravezetőbb a ternary operátort használni, hanem az "if" kifejezést a következőképpen:

if (condition) {
    // some fancy stuff here
} else {
    // some other stuff here
}

A fenti példában a zárójelben lévő feltétel (condition) kiértékelődik és egy boolean értéket kapunk rá, amely mint tudjuk igaz vagy hamis (true/false) lehet. Ha true volt, akkor az első kódblokk fog végrehajtódni, ha hamis volt, akkor az "else" utáni kódrész. Az else rész opcionális, azaz ha nem szeretnél semmi kódrészt futtatni ha a feltételed nem teljesült, akkor simán elhagyhatod. A program számára a döntés mindig egyértelmű, soha nem fog a trolli problémával szembesülni.

Csakúgy mint a ternary operátor esetén, a feltételek egymásba ágyazására lehetőség van:

if (condition) {
    // some fancy stuff here
} else {
    if (anotherCondition) {
        // some other stuff here
    } else {
        // neither condition is true
    }
}

Amennyiben az igaz vagy hamis ágon csak egyetlen kifejezést használunk, akkor lehetőségünk van elhagyni a blokkot (később erről még lesz szó) jelölő {} karaktereket:

if (condition) {
    // some fancy stuff here
} else if (anotherCondition) {
    // some other stuff here
} else {
    // neither condition is true
}

Amennyiben a feltételvizsgálatunk ugyanarra a változóra vonatkozik és egyenlőnséget vizsgálunk, de szeretnénk elkerülni az "if"-ek végeláthatatlan egymásba ágyazását, akkor lehetőségünk van egy sokkal olvashatóbb formát használni (switch-case block a szakkifejezés rá):

switch(x) {
  case 'value1':  // if (x === 'value1')
    ...
    [break]

  case 'value2':  // if (x === 'value2')
    ...
    [break]

  default:
    ...
    [break]
}

A fenti példában az "x" változót fogjuk összehasonlítani az egyes esetek (case-ek) értékével. Amennyiben egyezőség van, akkor a case-n belüli kódrész kezd el végrehajtódni, egészen addig amíg "break" utasítást nem talál vagy a switch block végére nem érünk. A break hatására a switch befejeződik (eltöri a program futását és kilép abból a blokkból). Mint látható a break használata opcionális, így akár több egymás utáni feltétel kódblokkjának végrehajtásásra is lehetőséged van ugyanazon switch-case blockon belül (ilyenkor már nem történik összehasonlítás!). Ha egyetlen feltétel sem teljesül, akkor a default rész fog lefutni, melynek használata szintén opcionális.

Truthy/Falsy

A legtöbb esetben a feltétel valamilyen összehasonlító operátort (vagy többet logikai operátorokkal összefűzve) fog tartalmazni, de nem minden esetben. Szintén az operátoros cikkben volt szó a típuskonverzióról, amikor egy típust átkonvertál nekünk a nyelv egy másik típusra. Ha a feltételünk csak egy változót tartalmaz, akkor a nyelv át fogja nekünk konvertálni boolean típussá annak értékétől függően. Ennek megfelelően beszélhetünk truthy (igaz) és falsy (hamis) értékekről:

Truthy:

  • true
  • []
  • {}
  • bármilyen nullánál kisebb vagy nagyobb szám (az Infinity és -Infinity is ide tartozik)
  • bármilyen szöveg, amelyik tartalmaz karaktert (nem üres a string)

Falsy:

  • false
  • 0 (és a teljesség kedvéért: a negatív nulla és a BigInt nulla)
  • üres string ("" vagy '')
  • null
  • undefined
  • NaN (Not a Number)

Alapvetően, hogy ne kelljen mindkét felsorolást megtanulnod elég ha megjegyzed a falsy értékeket, mert minden egyéb ami nem tartozik ide az truthy.

Ciklusok

Ha egy kódrészt többször szeretnénk lefutattani, akkor megtehetjük, hogy többször leírjuk egymás után (ami nem túl hatékony) vagy használhatunk ciklusokat, melyeknek több fajtája van és legtöbb esetben szabadon megválaszthatjuk, hogy melyiket használjuk.

while loop

A while azt jelenti: amíg. Szintaktikája:

while (condition) {
  // code
  // "loop body"
}

A loop body (magyarul ciklusmagra keresztelték) kódja addig fog futni amíg a feltétel igaz. A ciklusmag egyszeri lefutását iterációnak (iteration) nevezzük. Nézzünk egy egyszerű példát:

var i = 0
while (i < 10) {
    console.log("You are awesome!")
    i++
}

A fenti példában 10 iteráció történik, tízszer fut le a ciklusmag, tízszer lesz kiírva, hogy "You are awesome!". A fenti példát átírhatjuk olyan formára, hogy kihasználjuk a truthy/falsy boolean konverziót:

var i = 10
while (i) {
    console.log("You are awesome!")
    i--
}

A feltételünk fentebb akkor lesz false amikor az "i" változó eléri a nullát. Ezt az egyébként szuperkomplex programot még tovább tudjuk egyszerüsíteni:

var i = 10
while (i--) {
    console.log("You are awesome!")
}

Itt azt használjuk ki, hogy a decrement operátor (--) visszaadja a változó értékét a csökkentés előtt, amit aztán a nyelv - észlelve, hogy boolean-ra van szükség - átkonvertál nekünk a truthy/falsy szabálynak megfelelően boolean típusra. Mehetünk még ennél is tovább az egyszerüsítéssel? Igen, mégpedig úgy, hogy kihasználjuk az "if" kapcsán megismert lehetőséget, hogy ha csak 1 utasítás áll a feltétel mögött, akkor elhagyhatjuk a curly braces-t:

var i = 10
while (i--) console.log("You are awesome!")

do-while loop

do {
  // loop body
} while (condition)

Az egyetlen lényegi különbség a két forma között, hogy míg a while (condition) formánál előfordulhat, hogy a feltételünk egyből false (és ennek értelmében a ciklusmag egyszer sem fog lefutni) itt a ciklusmag egyszer lefut és csak utána ellenőrzi a feltételt.

A while loop először tesztel és utána futtat, a do-while pedig futtat majd tesztel, ezért ennek megfelelően szokás őket elöltesztelő és hátultesztelő ciklusoknak is nevezni.

for loop

A for ciklus mindhárom közül a legbonyolultabb, mégis ezt a formát használjuk a legtöbbet:

for (begin; condition; step) {
  // loop body
}

Nézzük melyik rész mit csinál:

  • begin: ez a rész egyszer fut le, amikor elkezdődik a ciklus
  • condition: a feltétel rész, amely meghatározza, hogy meddig fog futni a ciklusunk. Minden iterációban kiértékelődik és amint false lesz a ciklus befejeződik
  • loop body: az itt található kód fog ismétlődni
  • step: a loop body iterációja után ez a rész is végrehajtódik és kezdődik egy új iteráció a feltételvizsgálattal

A fenti példa for loop-os megvalósítása:

for (let i = 0; i < 10; i++) {
    console.log("You are awesome!")
}

A fenti példában a let i = 0 részt inline változó definiálásnak hívjuk és ez a változó csak a cikluson belül lesz elérhető (a változók hatóköréről lesz még később szó).

A részek közül bármelyik kihagyható, így például "visszabutíthatjuk" a for ciklusunkat a while ciklus szintjére:

var = 0
for (; i < 10;) {
    console.log("You are awesome")
    i++
}

Attól függetlenül, hogy az egyes részek nem tartalmaznak semmit a pontosvesszőt ki kell raknunk. Maga a feltételvizsgálat is kihagyható, ekkor a ciklusunk feltétel nélkül marad és "örökké" futni fog (végtelen ciklusként emlegetjük az ilyen ciklusokat). Butítsuk tovább a fenti ciklust:

var i = 0
for (;;) {
    if (i > 10) break
    console.log("You are awesome")
    i++
}

A fenti ciklusban a feltételvizsgálatot mi végeztük el az "if" kifejezéssel és mi léptünk ki a ciklusból a break segítségével (hasonlóan mint ahogy a switch-nél láttuk). Lehetőség van nem csak kilépni a ciklusból, hanem kényszeríteni a következő iteráció végrehajtására:

for (let i = 0; i < 10; i++) {

    /* ha a feltetel igaz, akkor 
    a loop body tobbi reszet hagyjuk ki,
    johet a step resz es a kovetkezo iteracio */
    if (i % 2 == 0) continue
  
    console.log(i) // 1, 3, 5, 7, 9
  }

Itt a remainder operátor segítségével a kiírásból kihagyjuk a páros számokat és csak a páratlanok lesznek kiiratva. (Álmos szerint most forgattam meg a matekosokat a sírjukban, hogy a nullát a páros számokhoz vettem).

Legtöbb esetben nullától indítjuk a ciklusainkat, ezért az "i" változót szokás ciklusszámlálónak is nevezni, mert ebben az esetben azt mutatja, hogy hányszor futott már le a ciklusunk (azért tartsd észben, hogy nem kötelező nullától indítani a változót).

Kombináljuk picit az eddigi tudásunkat és alkossunk valamit a tömbökkel és a ciklusokkal. Mivel a tömbök elemeit sorszámaikkal érjük el, kiváló lehetőség, hogy egy ciklus segítségével kiírjuk az értékeket:

var myArray = [
    'John Doe',
    'Jane Doe',
    'Doe Doe'
]

for (let i = 0; i < myArray.length; i++) {
    console.log(myArray[i])
}

Nézzünk egy példát jagged array-al is:

var myArray = [
    ['John Doe', 63, 'male'],
    ['Jane Doe', 60, 'female'],
    ['Doe Doe', 36, 'God knows']
]

for (let i = 0; i < myArray.length; i++) {
    for (let j = 0; j < myArray[i].length; j++) {
        console.log(myArray[i][j])
    }
}

Két ciklus lett egymásba ágyazva, ahol a külső (az "i" változós) a tömb egyes elemeit éri el (myArray[i]), míg a belső ciklus ezen elemeit járja be (myArray[i][j]).

Megtanultuk, hogy hogyan lehet egy ciklusból kilépni a break segítségével illetve hogyan tudunk új iterációt kérni. De mi van akkor, ha egymásba ágyazott ciklusoknál nem a belső, hanem a külső cikluson szeretnénk új iterációt kérni vagy nem csak a belső, hanem a külső ciklusból is ki akarunk lépni? Ilyenkor használhatunk címkéket (label) és megadhatjuk a continue és break utasításoknak, hogy mi arra a felcímkézett ciklusra gondolunk:

outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (someSeriousConditon) break outer
        // some cool stuff here
    }
}

Tipp Álmostól: bár elméletben lehetőségünk van nem egész számokkal is dolgozni a ciklusoknál, a számábrázolásból adódó pontatlanságok miatt ez mégsem célszerű: a böngésződ developer console-jában (F12-vel elő tudod hozni) írd be, hogy 0.1 + 0.2
Meg fogsz lepődni az eredményen.

Sajnos nagyon megszaladtak a sorok, ezért bizonyos dolgokat átvinnék a következő cikkbe. Jöjjön a szokásos összefoglaló:

Mit tanultál ebben a leckében?

  • megtanultad hogyan használd az if-else feltételvizsgálatot
  • megtanultad a truthy/falsy fogalmát, melyek kellenek a feltételvizsgálatokhoz
  • megismertél 3 alapvető ciklus formát
  • megtanultad, hogy miként lehet a ciklusokból kilépni illetve új iterációt kérni
  • megtanultad, hogy a ciklusok egymásba ágyazhatók és labelek segítségével bármilyen "mélységből" bárhova ki tudsz lépni amikor a break vagy continue utasítást használod