JavaScript yra vienos gijos kalba, kuri kartu yra ir neblokinė, asinchroninė ir lygiagreti. Šiame straipsnyje paaiškinsime, kaip tai vyksta.
Vykdymo laikas
JavaScript yra interpretuojama, o ne kompiliuojama kalba. Tai reiškia, kad jai reikia interpretatoriaus, kuris konvertuoja JSkodas į mašininį kodą. Yra kelių tipų interpretatoriai (vadinamieji varikliai). Populiariausi naršyklių varikliai yra V8 ("Chrome"), Quantum ("Firefox") ir WebKit ("Safari"). Beje, V8 taip pat naudojamas populiarioje ne naršyklės paleidimo sistemoje, Node.js.
Kiekviename variklyje yra atminties krūva, skambučių stekas, įvykių ciklas, atgalinių skambučių eilė ir WebAPI su HTTP užklausomis, laikmačiais, įvykiais ir t. t. Visa tai įgyvendinta savaip, kad JS kodas būtų greičiau ir saugiau interpretuojamas.
Pagrindinė JS paleidimo architektūra. Autorius: Autorius: Alex Zlatkov
Viena gija
Vienos gijos kalba - tai kalba su vienu iškvietimų kaminu ir viena atminties krūva. Tai reiškia, kad vienu metu joje vykdomas tik vienas dalykas.
A kamino yra ištisinė atminties sritis, kurioje kiekvienai vykdomai funkcijai priskiriamas vietinis kontekstas.
A krūva yra daug didesnis regionas, kuriame saugoma visa dinamiškai paskirstyta informacija.
A skambučių stekas yra duomenys struktūrą, kurioje iš esmės įrašoma, kurioje programos vietoje esame.
Skambučių stekas
Parašykime paprastą kodą ir stebėkime, kas vyksta skambučių steke.
Kaip matote, funkcijos įtraukiamos į steką, vykdomos ir vėliau ištrinamos. Tai vadinamasis LIFO būdas - paskutinis įeina, pirmas išeina. Kiekvienas skambučių steko įrašas vadinamas kamino rėmas.
Žinios apie iškvietimų steką yra naudingos skaitant klaidų steko pėdsakus. Paprastai tiksli klaidos priežastis yra pirmosios eilutės viršuje, nors kodo vykdymo tvarka yra iš apačios į viršų.
Kartais galite susidoroti su populiaria klaida, apie kurią pranešama Viršytas maksimalus skambučių kamino dydis. Tai lengva gauti naudojant rekursiją:
funkcija foo() {
foo()
}
foo()
ir mūsų naršyklė arba terminalas užstringa. Kiekviena naršyklė, net ir skirtingos jų versijos, turi skirtingą skambučių kamino dydžio ribą. Daugeliu atvejų jų pakanka ir problemos reikėtų ieškoti kitur.
Užblokuotas skambučių stekas
Čia pateikiamas JS gijos blokavimo pavyzdys. Pabandykime perskaityti foo failą ir baras naudojant Mazgas.js sinchroninė funkcija readFileSync.
Tai yra kilpinis GIF. Kaip matote, JS variklis laukia, kol bus atliktas pirmasis skambutis readFileSync baigtas. Tačiau tai neįvyks, nes nėra foo failą, todėl antroji funkcija niekada nebus iškviesta.
Asinchroninis elgesys
Tačiau JS taip pat gali neblokuoti ir elgtis taip, tarsi būtų daugiasluoksnė. Tai reiškia, kad ji nelaukia atsakymo į API iškvietimą, įvesties ir išvesties įvykius ir t. t., ir gali tęsti kodo vykdymą. Tai įmanoma dėl JS variklių, kuriuose naudojamos (po gaubtu) tikros daugiasluoksnės kalbos, pavyzdžiui, "C++" ("Chrome") arba "Rust" ("Firefox"). Jos suteikia mus su Tinklalapis API po naršyklės gaubtais arba pvz. I/O API po Node.js.
Pirmiau pateiktame GIF paveikslėlyje matome, kad pirmoji funkcija perkeliama į iškvietimų steką ir Sveiki iš karto įvykdomas konsolėje.
Tada iškviečiame setTimeout funkcija, kurią teikia naršyklės WebAPI. Ji patenka į skambučių steką ir asinchroninį grįžtamąjį skambutį foo funkcija patenka į "WebApi" eilę, kur laukia skambučio, nustatyto po 3 sekundžių.
Tuo tarpu programa tęsia kodą ir mes matome Sveiki. Aš nesu užblokuotas konsolėje.
Po to, kai ji iškviečiama, kiekviena WebAPI eilėje esanti funkcija patenka į Atgalinių iškvietimų eilė. Čia funkcijos laukia, kol iškvietimų stekas bus tuščias. Kai tai įvyksta, jos ten perkeliamos viena po kitos.
Taigi, kai mūsų setTimeout laikmatis baigia skaičiuoti laiką, mūsų foo funkcija patenka į atgalinio iškvietimo eilę, laukia, kol iškvietimų stekas taps prieinamas, patenka į jį, įvykdoma ir matome Sveiki iš asinchroninio grįžtamojo ryšio konsolėje.
Įvykių ciklas
Kyla klausimas, kaip vykdymo laikas sužino, kad iškvietimų stekas tuščias, ir kaip įvykis iškviečiamas atgalinio ryšio eilėje? Susipažinkite su įvykių ciklu. Tai JS variklio dalis. Šis procesas nuolat tikrina, ar iškvietimų stekas tuščias, ir, jei taip, stebi, ar grįžtamojo ryšio eilėje yra įvykis, laukiantis iškvietimo.
Tai visa magija, slypinti užkulisiuose!
Teorijos apibendrinimas
Vientisumas ir lygiagretumas
Konkuravimas reiškia kelių užduočių vykdymą vienu metu, bet ne vienu metu. Pvz., dvi užduotys atliekamos sutampančiais laikotarpiais.
Lygiagretumas tai dviejų ar daugiau užduočių atlikimas vienu metu, pvz., kelių skaičiavimų atlikimas vienu metu.
Siūlai ir procesai
Siūlai yra kodo vykdymo seka, kuri gali būti vykdoma nepriklausomai viena nuo kitos.
Procesas yra veikiančios programos egzempliorius. Programa gali turėti kelis procesus.
Sinchroninis ir asinchroninis
Svetainėje sinchroninis programavimas, užduotys vykdomos viena po kitos. Kiekviena užduotis laukia, kol bus baigta ankstesnė užduotis, ir tik tada yra vykdoma.
Svetainėje asinchroninis programavimas, kai viena užduotis įvykdyta, galite pereiti prie kitos užduoties, nelaukdami, kol bus baigta ankstesnė.
Sinchroninis ir asinchroninis darbas vieno ir kelių gijų aplinkoje
Sinchroninis su viena gija: Užduotys vykdomos viena po kitos. Kiekviena užduotis laukia, kol bus įvykdyta ankstesnė užduotis.
Sinchroninis su keliais srautais: Užduotys vykdomos skirtingose gijose, tačiau laukiama, kol bet kurioje kitoje gijoje bus įvykdytos kitos užduotys.
Asinchroninis su viena gija: Užduotys pradedamos vykdyti nelaukiant, kol baigsis kita užduotis. Vienu metu gali būti vykdoma tik viena užduotis.
Asinchroninis su keliais srautais: Užduotys vykdomos skirtingais srautais, nelaukiant, kol bus užbaigtos kitos užduotys, ir baigiamos vykdyti nepriklausomai.
JavaScript klasifikacija
Jei atsižvelgsime į tai, kaip JS varikliai veikia po gaubtu, galime priskirti JS asinchroninei ir vieno sriegio interpretuojamai kalbai. Žodis “interpretuota” yra labai svarbus, nes jis reiškia, kad kalba visada priklausys nuo vykdymo laiko ir niekada nebus tokia greita kaip sukompiliuotos kalbos su integruotais keliais srautais.
Pažymėtina, kad Node.js gali pasiekti realų daugiasluoksniškumą, jei kiekviena gija paleidžiama kaip atskiras procesas. Tam yra bibliotekų, tačiau Node.js turi integruotą funkciją, vadinamą Darbuotojų gijos.
Visi įvykio kilpos GIF failai yra iš Lupa Philipo Robertso sukurtą programą, kurioje galite išbandyti asinchroninius scenarijus.