JavaScript er et enkelttrådet språk og samtidig også ikke-blokkerende, asynkront og samtidig. Denne artikkelen forklarer hvordan det skjer.
Kjøretid
JavaScript er et tolket språk, ikke et kompilert språk. Det betyr at det trenger en tolk som konverterer JS kode til en maskinkode. Det finnes flere typer tolker (såkalte motorer). De mest populære nettlesermotorene er V8 (Chrome), Quantum (Firefox) og WebKit (Safari). V8 brukes for øvrig også i en populær kjøretid som ikke er nettleser, Node.js.
Hver motor inneholder en minnebunke, en anropsstabel, en hendelsesløkke, en tilbakekallingskø og en WebAPI med HTTP-forespørsler, tidtakere, hendelser osv., alt implementert på sin egen måte for raskere og sikrere tolkning av JS-koden.
Grunnleggende JS-arkitektur for kjøretid. Forfatter: Alex Zlatkov
Enkelttråd
Et språk med én tråd er et språk med én enkelt anropsstabel og én enkelt minnebunke. Det betyr at det bare kjører én ting om gangen.
A stabel er en kontinuerlig region i minnet som allokerer lokal kontekst for hver funksjon som kjøres.
A haug er en mye større region som lagrer alt som allokeres dynamisk.
A anropsstabel er en datastruktur som i utgangspunktet registrerer hvor vi befinner oss i programmet.
Anropsstabel
La oss skrive en enkel kode og følge med på hva som skjer i anropsstakken.
Som du kan se, legges funksjonene til i stakken, utføres og slettes senere. Det er den såkalte LIFO-måten - Last In, First Out. Hver oppføring i anropsstakken kalles en stabelramme.
Kunnskap om anropsstakken er nyttig for å lese feilstakkspor. Vanligvis står den nøyaktige årsaken til feilen øverst i første linje, selv om rekkefølgen på kodeutførelsen er nedenfra og opp.
Noen ganger kan du håndtere en populær feil, varslet av Maksimal størrelse på anropsstakken overskredet. Det er enkelt å få til dette ved hjelp av rekursivitet:
funksjon foo() {
foo()
}
foo()
og nettleseren eller terminalen vår fryser. Alle nettlesere, selv i ulike versjoner, har ulike grenser for størrelsen på anropsstakken. I de aller fleste tilfeller er de tilstrekkelige, og problemet bør søkes et annet sted.
Blokkert anropsstabel
Her er et eksempel på blokkering av JS-tråden. La oss prøve å lese en foo fil og en bar ved hjelp av Knutepunkt.js synkron funksjon readFileSync.
Dette er en loopet GIF. Som du ser, venter JS-motoren til det første kallet i readFileSync er fullført. Men dette vil ikke skje fordi det ikke finnes noen foo filen, slik at den andre funksjonen aldri vil bli kalt.
Asynkron oppførsel
JS kan imidlertid også være ikke-blokkerende og oppføre seg som om den var flertrådet. Det betyr at den ikke venter på svar på API-kall, I/O-hendelser osv. og kan fortsette kodekjøringen. Dette er mulig takket være JS-motorene som (under panseret) bruker ekte flertrådede språk, som C++ (Chrome) eller Rust (Firefox). De gir oss Web API under nettleserens hette eller f.eks. I/O API under Node.js.
I GIF-en ovenfor kan vi se at den første funksjonen skyves til anropsstakken og Hei utføres umiddelbart i konsollen.
Deretter kaller vi setTimeout funksjon som tilbys av nettleserens WebAPI. Den går til anropsstakken og dens asynkrone tilbakekalling foo funksjonen går til WebApi-køen, hvor den venter på anropet, som er satt til å skje etter 3 sekunder.
I mellomtiden fortsetter programmet koden, og vi ser Hei. Jeg er ikke blokkert i konsollen.
Etter at den er påkalt, går hver funksjon i WebAPI-køen til Tilbakekallingskø. Det er her funksjonene venter til anropsstakken er tom. Når det skjer, flyttes de dit én etter én.
Så når vår setTimeout timeren fullfører nedtellingen, vil vår foo funksjonen går til tilbakekallingskøen, venter til anropsstakken blir tilgjengelig, går dit, blir utført, og vi ser Hei fra asynkron tilbakekalling i konsollen.
Hendelsessløyfe
Spørsmålet er hvordan kjøretiden vet at anropsstakken er tom, og hvordan hendelsen i tilbakekallingskøen blir påkalt? Møt hendelsesløkken. Det er en del av JS-motoren. Denne prosessen sjekker hele tiden om anropsstakken er tom, og hvis den er det, overvåker den om det finnes en hendelse i tilbakeringingskøen som venter på å bli påkalt.
Det er all magien bak kulissene!
Oppsummering av teorien
Samtidighet og parallellisme
Samtidighet betyr at flere oppgaver utføres samtidig, men ikke simultant. F.eks. to oppgaver som utføres i overlappende tidsperioder.
Parallellisme betyr å utføre to eller flere oppgaver samtidig, f.eks. å utføre flere beregninger samtidig.
Tråder og prosesser
Tråder er en sekvens av kodeutførelser som kan utføres uavhengig av hverandre.
Prosess er en instans av et program som kjører. Et program kan ha flere prosesser.
Synkron og asynkron
I synkron programmering utføres oppgavene etter hverandre. Hver oppgave venter på at en eventuell tidligere oppgave er fullført, og utføres først da.
I asynkron programmering, kan du bytte til en annen oppgave når en oppgave er utført, uten å vente på at den forrige skal bli ferdig.
Synkront og asynkront i et enkelt- og flertrådet miljø
Synkron med en enkelt tråd: Oppgavene utføres én etter én. Hver oppgave venter på at den forrige oppgaven skal bli utført.
Synkron med flere tråder: Oppgaver utføres i forskjellige tråder, men venter på andre oppgaver som utføres i en annen tråd.
Asynkron med én enkelt tråd: Oppgaver begynner å bli utført uten å vente på at en annen oppgave skal bli ferdig. På et gitt tidspunkt kan bare én oppgave utføres.
Asynkron med flere tråder: Oppgaver utføres i forskjellige tråder uten å vente på at andre oppgaver skal fullføres, og de fullfører oppgavene sine uavhengig av hverandre.
JavaScript-klassifisering
Hvis vi ser på hvordan JS-motorene fungerer under panseret, kan vi klassifisere JS som et asynkront og entrådet tolket språk. Ordet "tolket" er svært viktig fordi det betyr at språket alltid vil være kjøretidsavhengig og aldri like raskt som kompilerte språk med innebygd flertrådning.
Det er verdt å merke seg at Node.js kan oppnå reell multitråding, forutsatt at hver tråd startes som en separat prosess. Det finnes biblioteker for dette, men Node.js har en innebygd funksjon som heter Arbeidertråder.
Alle hendelsesloop-GIF-er kommer fra Lupe applikasjon laget av Philip Roberts, der du kan teste dine asynkrone scenarier.