Hogyan működik az eseményhurok JavaScriptben?

Bár a teljes körű gyártási kód megírásához szükség lehet a nyelvek, például a C++ és a C alapos megértésére, a JavaScriptet gyakran úgy is meg lehet írni, hogy csak alapvető ismeretekkel rendelkezik arról, hogy mit lehet tenni a nyelvvel.

Az olyan koncepciókat, mint a visszahívások átadása a függvényeknek vagy az aszinkron kód írása, gyakran nem olyan nehéz megvalósítani, ami miatt a JavaScript-fejlesztők többsége kevésbé törődik azzal, hogy mi történik a motorháztető alatt. Egyszerűen nem törődnek azzal, hogy megértsék azokat a bonyolultságokat, amelyeket a nyelv mélyen elvont tőlük.

JavaScript-fejlesztőként egyre fontosabbá válik annak megértése, hogy mi történik valójában a motorháztető alatt, és hogyan működik a legtöbb tőlünk elvont bonyolultság. Segít megalapozottabb döntéseket hozni, ami viszont drasztikusan megnövelheti kódunk teljesítményét.

Ez a cikk a JavaScript egyik nagyon fontos, de ritkán érthető fogalmára vagy kifejezésére összpontosít. Az ESEMÉNYZÁR!.

Az aszinkron kód írása nem kerülhető el JavaScriptben, de miért is jelent valójában egy aszinkron módon futó kód? azaz az eseményhurok

Mielőtt megérthetnénk az eseményhurok működését, először is meg kell értenünk, mi maga a JavaScript, és hogyan működik!

Mi az a JavaScript?

Mielőtt továbblépnénk, szeretném, ha egy lépést tennénk vissza az alapokhoz. Mi is valójában a JavaScript? A JavaScriptet úgy definiálhatnánk, mint;

A JavaScript egy magas szintű, értelmezett, egyszálú, nem blokkoló, aszinkron, párhuzamos nyelv.

Várj, mi ez? Könyves meghatározás? 🤔

Bontsuk szét!

A cikkhez kapcsolódó kulcsszavak: egyszálas, nem blokkoló, párhuzamos és aszinkron.

Egyszálú

A végrehajtási szál a programozott utasítás legkisebb sorozata, amelyet az ütemező önállóan kezelhet. A programozási nyelv egyszálú, ami azt jelenti, hogy egyszerre csak egy feladatot vagy műveletet tud végrehajtani. Ez azt jelenti, hogy egy teljes folyamatot végrehajtana az elejétől a végéig anélkül, hogy a szálat megszakítanák vagy leállítanák.

Ellentétben a többszálú nyelvekkel, ahol több folyamat is futtatható több szálon egyidejűleg anélkül, hogy blokkolnák egymást.

Hogyan lehet a JavaScript egyszálú és nem blokkoló egyszerre?

De mit jelent a blokkolás?

Nem blokkoló

A blokkolásnak nincs egyetlen meghatározása; egyszerűen csak olyan dolgokat jelent, amelyek lassan futnak a szálon. Tehát a nem blokkolás olyan dolgokat jelent, amelyek nem lassúak a szálon.

  Hogyan lehet elérni a régi Myspace-fiókot e-mail-cím és jelszó nélkül

De várj, azt mondtam, hogy a JavaScript egyetlen szálon fut? És azt is mondtam, hogy nem blokkoló, ami azt jelenti, hogy a feladat gyorsan fut a hívásveremen? De hogyan??? Mit szólnál, ha időzítőket futtatunk? Hurok?

Lazíts! Kicsit megtudnánk 😉.

Egyidejű

A párhuzamosság azt jelenti, hogy a kódot egynél több szál egyidejűleg hajtja végre.

Oké, a dolgok tényleg kezdenek alakulni furcsa most hogyan lehet a JavaScript egyszálú és párhuzamos? azaz a kódját egynél több szállal hajtja végre?

Aszinkron

Az aszinkron programozás azt jelenti, hogy a kód eseményhurokban fut. Letiltó művelet esetén az esemény elindul. A blokkoló kód a fő végrehajtási szál blokkolása nélkül fut tovább. Amikor a blokkoló kód futása befejeződik, a sorba állítja a blokkoló műveletek eredményeként, és visszatolja őket a verembe.

De a JavaScript egyetlen szála van? Mi hajtja végre ezt a blokkoló kódot, miközben hagyja, hogy a szál többi kódja is végrehajtódjon?

Mielőtt folytatnánk, tekintsük át a fentieket.

  • A JavaScript egyszálú
  • A JavaScript nem blokkoló, azaz a lassú folyamatok nem akadályozzák a végrehajtását
  • A JavaScript párhuzamos, azaz egyszerre több szálban hajtja végre a kódját
  • A JavaScript aszinkron, azaz máshol blokkoló kódot futtat.

De a fentiek nem teljesen összeadódnak, hogyan lehet egy egyszálú nyelv nem blokkoló, párhuzamos és aszinkron?

Menjünk egy kicsit mélyebbre, menjünk le a JavaScript futásidejű motorjaira, a V8-ra, talán vannak olyan rejtett szálak, amelyekről nem tudunk.

V8 motor

A V8-as motor egy nagy teljesítményű, nyílt forráskódú webes összeállítási futásidejű motor a Google által C++ nyelven írt JavaScripthez. A legtöbb böngésző a V8 motor használatával futtatja a JavaScriptet, és még a népszerű node js futási környezet is használja.

Egyszerű angolul a V8 egy C++ program, amely JavaScript kódot kap, lefordítja és végrehajtja.

A V8 két fontos dolgot csinál;

  • Halom memória kiosztása
  • Verem végrehajtási kontextus hívása

Sajnos a gyanúnk téves volt. A V8-nak csak egy hívási verem van, gondoljon a hívási veremre, mint a szálra.

Egy szál === egy hívási verem === egyszerre egy végrehajtás.

Kép – Hacker Noon

  A Szerkesztői csevegés használata a Google Dokumentumokban

Mivel a V8 csak egy hívási veremből áll, hogyan fut a JavaScript párhuzamosan és aszinkron módon anélkül, hogy blokkolná a fő végrehajtási szálat?

Próbáljuk meg kideríteni egy egyszerű, de gyakori aszinkron kód írásával, és elemezzük együtt.

A JavaScript minden kódot soronként fut le, egymás után (egyszálú). Ahogy az várható volt, itt az első sor kerül kinyomtatásra a konzolon, de miért van az utolsó sor az időtúllépési kód előtt? Miért nem várja meg a végrehajtási folyamat az időtúllépési kódot (blokkolást), mielőtt az utolsó sort futtatná?

Úgy tűnik, hogy néhány másik szál segített végrehajtani az időtúllépést, mivel egészen biztosak vagyunk abban, hogy egy szál egy adott időpontban csak egyetlen feladatot tud végrehajtani.

Vessünk egy pillantást a V8 forráskód egy ideig.

Várj, mi??!!! A V8-ban nincs időzítő funkció, nincs DOM? Nincsenek események? Nincs AJAX?… Eeeeeesss!!!

Az események, a DOM, az időzítők stb. nem részei a JavaScript alapvető megvalósításának, a JavaScript szigorúan megfelel az Ecma Scripts specifikációinak, és különféle verzióira gyakran hivatkoznak az Ecma Scripts Specifications (ES X) szerint.

Végrehajtási munkafolyamat

Az eseményeket, az időzítőket és az Ajax kéréseket a böngészők biztosítják a kliens oldalon, és gyakran Web API-nak nevezik őket. Ezek azok, amelyek lehetővé teszik, hogy az egyszálú JavaScript nem blokkoló, párhuzamos és aszinkron legyen! De hogyan?

Bármely JavaScript-program végrehajtási munkafolyamatának három fő szakasza van: a hívási verem, a webes API és a feladatsor.

A Call Stack

A verem egy olyan adatstruktúra, amelyben mindig az utoljára hozzáadott elem kerül ki először a veremből, gondolhatjuk úgy is, mint egy olyan lemezköteget, amelyben csak az utoljára hozzáadott első lemez távolítható el először. A Call Stack egyszerűen nem más, mint egy verem adatstruktúra, amelyben a feladatok vagy a kód ennek megfelelően fut le.

Tekintsük az alábbi példát;

Forrás – https://youtu.be/8aGhZQkoFbQ

A printSquare() függvény meghívásakor a hívási verembe kerül, a printSquare() függvény meghívja a square() függvényt. A négyzet() függvény a verembe kerül, és meghívja a szorzás() függvényt is. A szorzás funkciót a veremre tolják. Mivel a szorzás függvény visszatér, és ez az utolsó dolog, amelyet a verembe toltak, ezért először ez kerül feloldásra, és eltávolításra kerül a veremből, ezt követi a square() függvény, majd a printSquare() függvény.

  A letöltési sebesség korlátozása a Chrome-ban

A webes API

Itt kerül végrehajtásra a V8-as motor által nem kezelt kód, hogy ne „blokkolja” a fő végrehajtási szálat. Amikor a Call Stack találkozik egy webes API funkcióval, a folyamat azonnal átadódik a webes API-nak, ahol végrehajtásra kerül, és felszabadítja a Call Stack-et, hogy végrehajtása során más műveleteket hajtson végre.

Térjünk vissza a fenti setTimeout példánkhoz;

A kód futtatásakor az első console.log sor a verembe kerül, és szinte azonnal megkapjuk a kimenetet, az időkorláthoz érve az időzítőket a böngésző kezeli, és nem része a V8 alapvető megvalósításának, hanem tolódik ehelyett a Web API-ra, felszabadítva a veremet, hogy más műveleteket hajthasson végre.

Amíg az időtúllépés még fut, a verem a következő műveletsorra lép, és lefuttatja az utolsó console.log fájlt, ami megmagyarázza, miért kapjuk ezt az időzítő kimenete előtt. Amint az időzítő lejár, történik valami. A console.log in aztán varázsütésre ismét megjelenik a hívási veremben!

Hogyan?

Az eseményhurok

Mielőtt az eseményhurkot tárgyalnánk, először menjünk végig a feladatsor funkcióján.

Visszatérve az időtúllépési példánkhoz, amint a webes API befejezi a feladat végrehajtását, nem csak automatikusan visszatolja a hívásverembe. A feladatsorba kerül.

A várólista egy olyan adatstruktúra, amely az Első az elsőben elven működik, így amikor a feladatok bekerülnek a sorba, ugyanabban a sorrendben kerülnek ki. A webes API-k által végrehajtott feladatok, amelyek a Task Queue-ba kerülnek, majd visszatérnek a Call Stack-be, hogy kinyomtassák az eredményt.

De várj. MI A SZÉN AZ ESEMÉNYZÁR???

Forrás – https://youtu.be/8aGhZQkoFbQ

Az eseményhurok egy olyan folyamat, amely megvárja, hogy a hívásverem tiszta legyen, mielőtt visszahívásokat küldene a feladatsorból a hívásverembe. Ha a verem kitisztult, az eseményhurok aktiválódik, és ellenőrzi a Task Queue elérhető visszahívásait. Ha vannak ilyenek, a hívásverembe tolja, megvárja, amíg a hívásverem újra tiszta lesz, és megismétli ugyanezt a folyamatot.

Forrás – https://www.quora.com/How-does-an-event-loop-work/answer/Timothy-Maxwell

A fenti diagram az eseményhurok és a feladatsor közötti alapvető munkafolyamatot mutatja be.

Következtetés

Bár ez egy nagyon alapvető bevezető, a JavaScript aszinkron programozásának fogalma elegendő betekintést nyújt ahhoz, hogy világosan megértsük, mi történik a motorháztető alatt, és hogyan képes a JavaScript párhuzamosan és aszinkron módon futni egyetlen szálon keresztül.

A JavaScript mindig igény szerinti, és ha kíváncsi a tanulásra, azt tanácsolom, hogy nézze meg ezt Udemy tanfolyam.