Javascript fejlesztőként úgy lehet a leghatékonyabban dolgozni, ha az ember tisztában van a forráskódját feldolgozó mechanizmussal.
Ebből a cikkből megtudhatod, hogy hogyan működik a javascript engine és betekintést nyerhetsz a böngésző más fontos alkotóelemeinek tevékenységébe is.
Amikor a böngészőbe megérkezik egy javascript forrásfájl, akkor annak végrehajtását a JavaScript Engine intézi.
Az engine egyszerre kizárólag csak egy sor kódot értelmez, más szóval szinkron végrehajtódású.
Emellett az a jellemzője, hogy a kódot fentről lefelé sorról sorra hajtja végre.
Az, hogy éppen hol tart éppen a végrehajtódás, más szóval melyik függvénynél van a kontroll, a call stack nevű mechanizmusban van nyilvántartva.
A call stack úgy működik, hogy amikor felszólítunk egy function-t a végrehajtódásra, akkor az a függvény rátolódik a call stack-re és amikor végetért a végrehajtódása, akkor lekerül a stack-ről.
Az engine, a program végrehajtását a globális kód futtatásával kezdi, ezt konvenció szerint main-nek, vagy global-nak nevezzük.
Amikor a globális kontextusban ráfutunk a console.log function hívásra, akkor végrehajtódik a log function, tehát rákerül a stack-re, majd amikor lefutott lekerül onnan.
Ezután meghívódik az első függvény így az kerül rá a call stack-re és megkapja a kontrollt.
A végrehajtódás ilyen módon folytatódik tovább, mindig amelyik függvényt meghívódik ahhoz kerül a kontroll és ha lefutott, akkor visszakerül a kontroll abba a függvény kontextusba, ahonnan meg lett hívva.
Végsősoron, amikor minden sor kód végrehajtódott a globális kontextusban is, akkor a script futása végetér.
Az addEventListener function
Felmerülhet azonban a kérdés, hogy hogyan lehet hogy a javascriptes programokban a működés sokszor párhuzamosnak tűnik?
Például mi történik az engine-ben akkor, amikor gombnyomás eseményre hajtódik végre valami funkcionalitás?
A válasz erre a következő:
A javascript engine képes feladatokat rábízni az őt körülölelő böngésző programra.
Mindig amikor valami eseményre kell reagálni, vagy egy hosszú ideig tartó műveletet kell végrehajtani, az engine beregisztrálja az adott feladatot a böngészőnek
A böngésző az engine futásával párhuzamosan el tudja látni a rábízott feladatokat, ilyen módon az enginnek nem kell leállnia foglalkozni ezekkel a műveletekkel.
Ha közvetlenül ő fogalalkozna velük, akkor azzal hosszú ideig blokkva lenne a stack és egy időre megszűnne az oldalunkon minden interaktív működés.
A web api-k összefoglaló néven a böngésző által nyújtott azon eszköztár, aminek segítségével az engine feladatokat tud beregisztrálni.
Mindig amikor valamely beregisztált feladat kapcsán valami fejlemény történik, a böngésző a callback queue-ba helyez el üzeneteket.
Amíg az engine-ben tart a szinkron futás, addig a queue-ban lévő üzenetek a bekerülés sorrendjében várakoznak.
A felgyűlő üzenetek kapcsán elvégzendő feladatok végrehajtását az event loop nevű eszköz indítványozza.
Az event loop úgy működik, hogy folyamatosan nézi egyszerre a stack-et és a callback queue-t és amint azt látja, hogy a stack kiürült és a callback queue-ban éppen van elem, akkor kiveszi a soron következő elemet a queue-ból és rátolja a stackre.
Itt a szokásos szinkron működést láthatjuk, viszont ez esetben az addEventlistener függvény futása során, az engine beregisztrál egy feladatot a böngészőnek, majd folytatja a többi kód végrehajtását.
Végsősoron a call stack kiürül.
Innentől kezdve akármikor klikk esemény történik a megadott elementre, a böngésző berak egy értesítést a callback queue-ba.
Az event loop ha éppen nem lát a stack-en semmit, akkor kiszedi a soron következő elemet a queue-ból és rátolja a stack-re.
Ezután ismét szinkron végrehajtódás veszi kezdetét, egészen addig amíg le nem ürül a stack.
Ha leürült, akkor a loop további elemeket tesz rá és ez így folytatódik, egészen addig amíg a queue ki nem ürült.
A web apik révén megannyi más hasonló jellegű feladat beregisztrálható, ezekkel munkád során sokszor találkozhatsz.
Ide tartozik a setTimeout, a setInterval és az AJAX kérések is.
A setTimeout function
Egy utolsó példa; mi történik ha meghívjuk a setTimeout function-t, bedobjuk a késleltetni kívánt függvényt és 0ms-ot adunk meg második paraméterként.
Elsőre azt gondolhatnánk, hogy a bedobott függvény azonnal futni fog, mert a 0ms azonnal letelik, de valójában az előbb felvázolt mechanizmus szerint fog alalkulni a kód futása.
Tehát a setTimeout function hívásával regisztrálunk egy feladatot a böngészőnek és a 0ms megadásával csak annyi történik, hogy a callback queue-ba szinte azon nyomban bekerül az üzenet.
Viszont miután az event loop csak a stack leürülése után tesz rá elemet a stack-re, a setTimeoutba dobott függvény végrehajtódása később fog történni.
Ezt a logban is láthatjuk, a második function-ben kilogolt üzenetek íródnak ki utoljára.
Ha ezt a felvázolt rendszert átlátod, akkor elkerülheted azt a tipikus hibát, hogy a kód szinkron módon lefutó részéből próbálsz hivatkozni egy olyan értékre, ami csak a jövőben lesz majd elérhető.
Tekintsd meg a teljes videót: