JavaScript pod maską: pętla zdarzeń
Opanujmy JavaScript, badając jego działanie od podstaw
Czy kiedykolwiek napotkałeś niezdefiniowane błędy lub miałeś trudności z określeniem zakresu zmiennej?
Debugowanie błędów bez wiedzy o tym, jak działa kod, jest naprawdę czasochłonne.
Na tym blogu pokażę, jak działają zaawansowane koncepcje, takie jak event loop, execution context, call stacki callback queue.
Zastrzeżenie — koncepcje wymagają ogromnej wiedzy i są ze sobą powiązane, więc proszę, nawet nie mrugaj!
Silnik JavaScript
Silnik Google V8 jest dobrze znaną ilustracją silnika JavaScript. Na przykład Chrome i Node.js wykorzystują silnik V8. Zasadniczo silnik V8 składa się z dwóch części —
- Stos wywołań: Cały kod jest wykonywany w nim. Działa jak struktura danych Stack, co oznacza podążanie za koncepcją LIFO (Last In First Out).
- Sterta pamięci: Miejsce , w którym następuje alokacja pamięci. W przeciwieństwie do sterty struktury danych, jest to tylko pamięć.
Globalny kontekst wykonania lub GEC jest domyślnym kontekstem wykonania tworzonym przez silnik JavaScript za każdym razem, gdy odbierany jest plik skryptu.
Cały kod JavaScript, który nie znajduje się wewnątrz funkcji, jest wykonywany w formacie GEC. Następujące kroki są wykonywane w GEC—
- Skonstruuj przestrzeń pamięci do przechowywania wszystkich zmiennych i funkcji w skali globalnej
- Wygeneruj obiekt globalny.
- Wygeneruj słowo kluczowe
this
W zależności od tego, gdzie twój kod zostanie wykonany, określi, gdzie thissię znajduje. Na przykład w Node.js wskazuje na odrębny obiekt globalny, podczas gdy w przeglądarce wskazuje na ten windowobiekt.
Stos wywołań (lub stos wykonywania funkcji)
JavaScript jest single-threaded, jak już słyszałeś. Ale co to właściwie znaczy?
Oznacza to, że silnik JavaScript zawiera tylko jeden call stacklub function execution stack.
- Jak wiemy, za każdym razem, gdy kompilator po raz pierwszy eksploruje twój kod, silnik JS jest proszony o utworzenie
Global Execution ContextlubGECprzez kompilator, a także o umieszczenie go w plikuCall Stack. - Cały twój kod jest wykonywany jeden po drugim w programie
Global Execution Contexti przydziela pamięć na definicję funkcji lub deklarację zmiennej i tam ją przechowuje. - Ale kiedy zostanie znalezione wywołanie funkcji, tworzony jest
Functional Execution Contextlub wFECcelu wykonania kodu funkcji, a następnie jest dodawany na początku plikucall stack. - Interpreter usuwa funkcję za
call stackkażdym razem, gdy funkcja się kończy. Funkcja kończy się — gdy osiągnie koniec swojego zakresu lub instrukcji return. - Wreszcie wykonanie całego kodu
GECjest usuwane z plikuCall Stack.
Nie martw się, zademonstrujmy to na przykładzie.
function f1{
console.log('f1');
}
f1();
console.log('end');
Krok 2: — W naszym przykładzie zostanie wykonana pierwsza linia, czyli f1. Zostanie przydzielona pamięć f1i zapisana jej definicja.
Krok 3: — W drugiej linii wywoływana jest funkcja. Dla tego wywołania funkcji zostanie utworzone Function Execution Context or FECi będzie ono przechowywane na górze pliku Call Stack.
Krok 4: — Teraz całość f1()zostanie wykonana linia po linii i po zakończeniu wykonania zostanie usunięta z pliku Call Stack.
Krok 5: — Następnie ostatnia linia console.log('end')zostanie wykonana i wyświetli „end” na konsoli. Na koniec wykonanie całego kodu Global Execution Contextzostanie usunięte z pliku Call Stack.
Jak JS zarządza zadaniami asynchronicznymi?
JavaScript, jak wszyscy wiemy, jest synchronicznym , jednowątkowym językiem (jedno zadanie na raz), a pojedynczy wątek, który jest call stackwykonywany, natychmiast wykonuje wszystko, co się w nim znajduje.
Ale co, jeśli musimy wykonać coś po 5 sekundach? Możemy to założyć w środku call stack?
Nie, nie możemy. Ponieważ call stacknie ma żadnego timera. Ale jak możemy to zrobić?
W tym miejscu pojawia się środowisko wykonawcze JavaScript .
Może być złamane serce, jeśli powiem ci teraz - nie jest częścią JavaScript , setTimeout()mimo że wszystkie zdarzenia DOM są częściami Web API , które dają dostęp do JavaScript Engine w celu korzystania ze wszystkich jego właściwości w Global Execution Context (GEC) poprzez obiekt globalny . Te internetowe interfejsy API są określane jako asynchroniczne .console.log()window
Co to jest kolejka wywołań zwrotnych?
Kolejka zadań zwana „kolejką wywołań zwrotnych” lub „kolejką zadań” jest wykonywana po zakończeniu bieżących zadań stosu wywołań. Zadania zarejestrowane w internetowym interfejsie API są przenoszone z internetowego interfejsu API do kolejki wywołania zwrotnego.
Kolejka zwrotna działa jak struktura danych kolejki, co oznacza, że zadania są obsługiwane w kolejności FIFO (pierwsze weszło, pierwsze wyszło), w przeciwieństwie do stosu wywołań, co oznacza, że zadania są obsługiwane w kolejności, w jakiej zostały dodane do kolejki.
Co to jest pętla zdarzeń?
Pętla zdarzeń JavaScript dodaje zadanie z kolejki wywołań zwrotnych do stosu wywołań w kolejności FIFO, gdy tylko stos wywołań jest pusty.
Pętla zdarzeń jest blokowana, jeśli stos wywołań aktualnie wykonuje jakiś kod i nie doda dodatkowych wywołań z kolejki, dopóki stos ponownie nie będzie pusty. Dzieje się tak, ponieważ kod JavaScript jest uruchamiany w sposób „od uruchomienia do ukończenia”.
Zrozummy powyższe pojęcia na przykładzie.
- Na początku
Global Execution Contextjest tworzony dla naszego kodu wewnątrz naszegocall stackiGECwykonuje nasz kod linia po linii. GECwykonuje pierwszą linię i wyświetla „Start” na naszej konsoli.- Wykonywanie drugiej linii
setTimeout()spowoduje wywołanie internetowego interfejsu API, a następniesetTimeout()udostępni funkcję timera. Wtedy będziesz mógł ustawić5000msjako czas opóźnienia. - Gdy przekazujesz
callBack()funkcję przezsetTimeout(),callBack()zostanie ona zarejestrowana jako interfejs API sieci Web wywołania zwrotnego. - A następnie
GECwykonuje pierwszą linię, a także drukuje „Koniec” na konsoli. - Gdy cały kod zostanie wykonany,
GECzostanie usunięty z naszegocall stack. - Następnie
5000 millisecondfunkcjacallBack()zarejestrowana wweb API, jest przenoszona docall Back Queue. Event loopumieszczacallBack()funkcję wcall Stackkiedy to się skończy, wszystko działa. Na konieccallBack()funkcja jest wykonywana i drukuje „Oddzwoń” w konsoli.
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
}
function main() {
console.log('main');
setTimeout(f1, 0);
f2();
}
main();
Jeśli myślisz, że „f1” zostanie wydrukowane przed „f2”, to się mylisz. To będzie -
main
f2
f1
Omawiany mechanizm JavaScript nadaje się do dowolnej funkcji zwrotnej lub żądania API.
Przyjrzyjmy się krok po kroku, jak drugi przykład działa w środowisku wykonawczym.
- Najpierw
GECzostanie utworzony wewnątrz,call stacka następnie kod zostanie wykonany linia po linii wGEC. Przechowuje wszystkie definicje funkcji w Stercie Pamięci . - Kiedy
main()wywoływanyFunction Execution Context (FEC)jest, tworzony jest a, a następnie trafia do stosu wywołań. Następnie cały kodmain()funkcji będzie wykonywany linia po linii. - Ma dziennik konsoli, aby wydrukować słowo main. Więc
console.log('main')wykonuje się i wychodzi ze stosu. - Interfejs
setTimeout()API przeglądarki ma miejsce. Funkcja wywołania zwrotnego umieszcza go w kolejce wywołania zwrotnego. Ale na stosie wykonanie odbywa się jak zwykle, więcf2()trafia do stosu. Dziennik konsoli zf2()wykonaniami. Oba wypadają ze stosu. - A potem
main()również wyskakuje ze stosu. - Pętla zdarzeń rozpoznaje, że stos wywołań jest pusty, aw kolejce znajduje się funkcja wywołania zwrotnego. Tak więc funkcja wywołania zwrotnego
f1()zostanie umieszczona na stosie przezevent loop. Rozpoczyna się egzekucja. Dziennik konsoli jest wykonywany, af1()także wyskakuje ze stosu. I wreszcie, nic innego nie znajduje się na stosie i w kolejce do dalszego wykonania.
To moja praktyka i uwaga. Jeśli uznasz to za przydatne, okaż swoje wsparcie, klikając ikonę klaskania poniżej. Możesz śledzić mnie na medium .

![Czym w ogóle jest lista połączona? [Część 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































