Jak działa pętla zdarzeń w JavaScript?

Chociaż napisanie pełnowymiarowego kodu produkcyjnego może wymagać dogłębnego zrozumienia języków, takich jak C++ i C, JavaScript często można napisać, mając tylko podstawowe zrozumienie tego, co można zrobić z tym językiem.

Koncepcje, takie jak przekazywanie wywołań zwrotnych do funkcji lub pisanie kodu asynchronicznego, często nie są tak trudne do zaimplementowania, co sprawia, że ​​większość programistów JavaScript mniej przejmuje się tym, co dzieje się pod maską. Po prostu nie dbają o zrozumienie złożoności, które zostały im głęboko wyabstrahowane przez język.

Dla programisty JavaScript coraz ważniejsze staje się zrozumienie, co tak naprawdę dzieje się pod maską i jak naprawdę działa większość tych wyabstrahowanych od nas zawiłości. Pomaga nam podejmować bardziej świadome decyzje, co z kolei może drastycznie zwiększyć wydajność naszego kodu.

Ten artykuł skupia się na jednym z bardzo ważnych, ale rzadko rozumianych pojęć lub terminów w JavaScript. PĘTLA WYDARZEŃ!.

Pisania kodu asynchronicznego nie da się uniknąć w JavaScript, ale dlaczego kod działający asynchronicznie naprawdę oznacza? czyli Pętla Zdarzeń

Zanim zrozumiemy, jak działa pętla zdarzeń, musimy najpierw zrozumieć, czym jest sam JavaScript i jak działa!

Co to jest JavaScript?

Zanim przejdziemy dalej, chciałbym, żebyśmy cofnęli się do podstaw. Czym tak naprawdę jest JavaScript? Możemy zdefiniować JavaScript jako;

JavaScript to wysokopoziomowy, interpretowany, jednowątkowy, nieblokujący, asynchroniczny, współbieżny język.

Czekaj, co to jest? Książkowa definicja? 🤔

Rozbijmy to!

Słowa kluczowe tutaj w odniesieniu do tego artykułu to jednowątkowy, nieblokujący, współbieżny i asynchroniczny.

Pojedynczy wątek

Wątek wykonania to najmniejsza sekwencja zaprogramowanej instrukcji, którą może zarządzać niezależnie program planujący. Język programowania jest jednowątkowy, co oznacza, że ​​może wykonywać tylko jedno zadanie lub operację w jednym czasie. Oznacza to, że wykonałby cały proces od początku do końca bez przerywania lub zatrzymywania wątku.

W przeciwieństwie do języków wielowątkowych, w których wiele procesów może być uruchamianych jednocześnie na kilku wątkach bez wzajemnego blokowania.

W jaki sposób JavaScript może być jednocześnie jednowątkowy i nieblokujący?

Ale co oznacza blokowanie?

Bez blokowania

Nie ma jednej definicji blokowania; oznacza to po prostu rzeczy, które działają wolno w wątku. Tak więc brak blokowania oznacza rzeczy, które nie spowalniają wątku.

Ale poczekaj, czy powiedziałem, że JavaScript działa w jednym wątku? Powiedziałem też, że nie blokuje, co oznacza szybkie uruchamianie zadania na stosie wywołań? Ale jak??? A kiedy uruchomimy stopery? Pętle?

Zrelaksować się! Dowiemy się za chwilę 😉.

Równoległy

Współbieżność oznacza, że ​​kod jest wykonywany współbieżnie przez więcej niż jeden wątek.

Dobra, sprawy mają się naprawdę dobrze dziwny teraz, w jaki sposób JavaScript może być jednowątkowy i jednocześnie współbieżny? tj. wykonywanie jego kodu z więcej niż jednym wątkiem?

Asynchroniczny

Programowanie asynchroniczne oznacza, że ​​kod działa w pętli zdarzeń. W przypadku operacji blokującej zdarzenie jest uruchamiane. Kod blokujący działa bez blokowania głównego wątku wykonawczego. Kiedy kod blokujący zakończy działanie, kolejka jest wynikiem operacji blokujących i odkłada je z powrotem na stos.

Ale JavaScript ma jeden wątek? Co następnie wykonuje ten kod blokujący, jednocześnie pozwalając na wykonanie innych kodów w wątku?

Zanim przejdziemy dalej, zróbmy podsumowanie powyższego.

  • JavaScript jest jednowątkowy
  • JavaScript nie blokuje, tzn. powolne procesy nie blokują jego wykonania
  • JavaScript jest współbieżny, tzn. wykonuje swój kod w więcej niż jednym wątku jednocześnie
  • JavaScript jest asynchroniczny, tzn. uruchamia blokujący kod w innym miejscu.

Ale powyższe nie do końca się sumuje, w jaki sposób język jednowątkowy może być nieblokujący, współbieżny i asynchroniczny?

Zejdźmy trochę głębiej, przejdźmy do silników wykonawczych JavaScript, V8, być może ma jakieś ukryte wątki, o których nie jesteśmy świadomi.

Silnik V8

Silnik V8 to wysokowydajny silnik środowiska uruchomieniowego zestawu sieci Web typu open source dla języka JavaScript napisany w języku C++ przez firmę Google. Większość przeglądarek obsługuje JavaScript przy użyciu silnika V8, a nawet popularne środowisko wykonawcze node js również go używa.

W prostym języku angielskim V8 to program C++, który otrzymuje kod JavaScript, kompiluje go i wykonuje.

V8 robi dwie główne rzeczy;

  • Alokacja pamięci sterty
  • Kontekst wykonania stosu wywołań

Niestety, nasze podejrzenia były błędne. V8 ma tylko jeden stos wywołań, pomyśl o stosie wywołań jako o wątku.

Jeden wątek === jeden stos wywołań === jedno wykonanie naraz.

Obraz – Haker w południe

Ponieważ V8 ma tylko jeden stos wywołań, w jaki sposób JavaScript działa współbieżnie i asynchronicznie bez blokowania głównego wątku wykonawczego?

Spróbujmy się tego dowiedzieć, pisząc prosty, ale powszechny kod asynchroniczny i wspólnie go analizując.

JavaScript uruchamia każdą linię kodu linia po linii, jedna po drugiej (jednowątkowy). Zgodnie z oczekiwaniami, pierwsza linia jest tutaj drukowana w konsoli, ale dlaczego ostatnia linia jest drukowana przed kodem limitu czasu? Dlaczego proces wykonywania nie czeka na kod limitu czasu (blokowanie) przed wykonaniem ostatniego wiersza?

Wydaje się, że jakiś inny wątek pomógł nam wykonać ten limit czasu, ponieważ jesteśmy prawie pewni, że wątek może wykonać tylko jedno zadanie w dowolnym momencie.

Rzućmy okiem na Kod źródłowy V8 przez chwilę.

Czekaj, co??!!! W V8 nie ma funkcji timera, nie ma DOM? Brak wydarzeń? Bez AJAX-a?…. Jeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee!!!

Zdarzenia, DOM, liczniki czasu itp. nie są częścią podstawowej implementacji JavaScript, JavaScript jest ściśle zgodny ze specyfikacjami skryptów Ecma, a różne jego wersje są często przywoływane zgodnie ze specyfikacjami skryptów Ecma (ES X).

Przepływ pracy wykonania

Zdarzenia, liczniki czasu, żądania Ajax są dostarczane po stronie klienta przez przeglądarki i są często określane jako Web API. To dzięki nim jednowątkowy JavaScript może być nieblokujący, współbieżny i asynchroniczny! Ale jak?

Istnieją trzy główne sekcje przepływu pracy dowolnego programu JavaScript: stos wywołań, internetowy interfejs API i kolejka zadań.

Stos wywołań

Stos to struktura danych, w której ostatni dodany element jest zawsze usuwany ze stosu jako pierwszy. Można o nim myśleć jako o stosie talerzy, z którego tylko pierwszy dodany talerz może zostać usunięty jako pierwszy. Stos wywołań to po prostu struktura danych stosu, w której odpowiednio wykonywane są zadania lub kod.

Rozważmy poniższy przykład;

Źródło – https://youtu.be/8aGhZQkoFbQ

Kiedy wywołujesz funkcję printSquare() , jest ona umieszczana na stosie wywołań, funkcja printSquare() wywołuje funkcję square(). Funkcja square() jest umieszczana na stosie i wywołuje również funkcję multiple(). Funkcja mnożenia jest umieszczana na stosie. Ponieważ funkcja mnożenia powraca i jest ostatnią rzeczą, która została odłożona na stos, jest najpierw rozwiązywana i usuwana ze stosu, po której następuje funkcja square() i funkcja printSquare().

Internetowy interfejs API

W tym miejscu wykonywany jest kod, który nie jest obsługiwany przez silnik V8, aby nie „blokować” głównego wątku wykonawczego. Gdy Call Stack napotka funkcję web API, proces jest natychmiast przekazywany do Web API, gdzie jest wykonywany i zwalnia Call Stack do wykonywania innych operacji podczas jego wykonywania.

Wróćmy do naszego przykładu setTimeout powyżej;

Kiedy uruchamiamy kod, pierwsza linia console.log zostaje wypchnięta na stos i prawie natychmiast otrzymujemy nasze dane wyjściowe, po osiągnięciu limitu czasu timery są obsługiwane przez przeglądarkę i nie są częścią podstawowej implementacji V8, są wypychane zamiast tego do interfejsu API sieci Web, zwalniając stos, aby mógł wykonywać inne operacje.

Podczas gdy limit czasu wciąż trwa, stos przechodzi do następnej linii działania i uruchamia ostatni plik console.log, co wyjaśnia, dlaczego otrzymujemy ten wynik przed wyjściem timera. Po zakończeniu odmierzania czasu coś się dzieje. Console.log in, a następnie timer magicznie pojawia się ponownie na stosie wywołań!

Jak?

Pętla zdarzeń

Zanim omówimy pętlę zdarzeń, omówimy najpierw funkcję kolejki zadań.

Wracając do naszego przykładu przekroczenia limitu czasu, gdy interfejs API sieci Web zakończy wykonywanie zadania, nie tylko automatycznie wypycha je z powrotem do stosu wywołań. Przechodzi do kolejki zadań.

Kolejka to struktura danych, która działa na zasadzie pierwsze weszło, pierwsze wyszło, więc gdy zadania są umieszczane w kolejce, wychodzą w tej samej kolejności. Zadania, które zostały wykonane przez Web API, które są wypychane do kolejki zadań, a następnie wracają do stosu wywołań, aby wydrukować ich wynik.

Ale poczekaj. CZYM DO CHOLERY JEST PĘTLA ZDARZEŃ???

Źródło – https://youtu.be/8aGhZQkoFbQ

Pętla zdarzeń to proces, który czeka na wyczyszczenie stosu wywołań przed przekazaniem wywołań zwrotnych z kolejki zadań do stosu wywołań. Po wyczyszczeniu stosu pętla zdarzeń uruchamia się i sprawdza kolejkę zadań pod kątem dostępnych wywołań zwrotnych. Jeśli jakieś istnieją, wypycha je do stosu wywołań, czeka na ponowne usunięcie stosu wywołań i powtarza ten sam proces.

Źródło – https://www.quora.com/How-does-an-event-loop-work/answer/Timothy-Maxwell

Powyższy diagram przedstawia podstawowy przepływ pracy pomiędzy Pętlą Zdarzeń a Kolejką Zadań.

Wniosek

Chociaż jest to bardzo podstawowe wprowadzenie, koncepcja programowania asynchronicznego w JavaScript daje wystarczający wgląd, aby jasno zrozumieć, co dzieje się pod maską i jak JavaScript może działać współbieżnie i asynchronicznie z tylko jednym wątkiem.

JavaScript jest zawsze na żądanie, a jeśli chcesz się czegoś nauczyć, radzę ci to sprawdzić Kurs Udemy.