Let there be a cool variable (JS)

 

Nemrégiben tettem fel facebookon a találós kérdést, hogy mi lesz a végeredménye ennek a kódnak:

for (var i=0; i < 3; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}

Érkezett helyes megoldás azonban a "miért" nem teljesen pontos. Szóval öntsünk tiszta vizet a pohárba.

A helyes válasz "természetesen" az hogy 3 3 3. Pedig sokan a 0 1 2 -őt várnák megoldásnak. Nem csoda, ez az egyik sarkalatos pontja a JavaScript nyelvnek ami miatt oly sokan utálják. Aki már jó ideje használja a JS-et az természetesen ismeri ezeket a "trükköket".

 

Na vizsgáljuk meg közelebbről, hogy mi történik valójában:

  • A for ciklus, ciklusváltozója az i.
  • A ciklusmagon belül kiadott setTimeout utasítás egy késleltetett futtatást tesz lehetővé.
  • A benne megadott névtelen függvény kódja háromszor fog lefutni késleltetve. Az első 0 a második 100 míg a harmadik 200ms múlva.
  • A névtelen függvény kódja kiírja a konzolra az i változó értékét.

Na de milyen értéket! Mind a három kiíratáskor az i ugyanazt a változót jelöli, ugyanarra a memóriaterületre "mutat". Azonban mindhárom kiíratás késleltetve fut le, akkor amikor az i változó értéke már 3. Tehát magyarán a ciklus hamarabb lefut mint a ciklusmagban lévő kiíratás, amikor is az i értéke már a végérték+1 -et tartalmazza.

 

Hogy miért 3 és miért nem 2?

Nos aki nálam tanult bevezetés a programozásba című tárgyat, az tudja, hogy a for ciklus, ciklusváltozója a ciklus lefutása után (szabályos befejezés esetén) azt az értéket tárolja
- amelyre utoljára futott le a ciklus
vagy
- amire már éppen nem futott le a ciklus.

Hogy melyik az igaz? Az attól függ. Nyelvenként eltérő. Mindenesetre a JS az utóbbit vallja.
Tehát 3 lesz az értéke, mert erre már nem fut le a ciklus. Nem teljesíti a feltételt.

 

Hogyan érhetem el a 0 1 2 eredményt?

Több eshetőség is van. Az első példában a leggyakoribb megoldást használjuk az IIFE (Immediately Invoked Function Expression) technikát. Ebben az esetben a ciklusmag kódját "bebugyoláljuk" egy függvénybe (zölddel jelölve a kódban) aminek az i ciklusváltozót átadjuk paraméterül, és ezt a szintén névtelen bebugyolált függvényt azonnal meg is hívjuk. Ezáltal az i hatásköre a függvényhez fog kötődni ami a megfelelő értéket adja neki mindhárom esetben.

Íme a kód:

for (var i=0; i < 3; i++) {
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}

Tudom, nem túl szép, de működik az összes JS verzióval.

 

Szépítsünk picit és használjuk az új Ecmascript6 adta lehetőségeket:

for (let i=0; i < 3; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}

A var kulcsszó helyett a let-et használva egyből megoldódik a problémánk. cool Hát nem szép? De. Csak sok böngésző még nem támogatja.
Főleg az IE csak a 11-es verziótól kezdve. Bővebb infó itt: caniuse.com

 

Nos ha már Ecmascript6 akkor szépítsük tovább és használjuk az arrow függvényt. Ez most úgy is divatba jött több programnyelvnél is:

for (let i=0; i < 3; i++) {
setTimeout(() => console.log(i*100), 100 * i);
}

 

Ezzel végére is értünk a probléma elemzésének. Remélem sikerült mindent tisztázni ezzel kapcsolatban.

 

Zárógondolatként pedig egy link. Amikor JS-ben leírom a let kulcsszót, nekem mindig ez ugrik be laughing

youtube

 

2019 © Robert Girhiny