Głębokie zanurzenie się w JavaScript Internals
Wewnętrzne działanie silnika JavaScript, pętla zdarzeń, interfejsy API sieci Web, kolejki zdarzeń i wywołania zwrotne.
Wstęp
Czy kiedykolwiek wyobrażałeś sobie, co by się stało, gdyby nie było Javascript? Bez Javascript nie można by przesyłać strumieniowo filmów na Netflix, robić zakupów na Amazon czy oglądać filmów na Youtube. Niemal co druga witryna korzysta z JavaScriptu i sprawia, że strona jest interaktywna.
Zanim wynaleziono JavaScript, Internet składał się tylko ze zwykłych stron HTML . HTML i CSS nadają strukturę i styl stronie internetowej. Ale Javascript sprawia, że strona jest dynamiczna . Możesz wprowadzić swoje dane uwierzytelniające i zalogować się na stronie internetowej, przeprowadzać transakcje online lub grać w gry. JavaScript sprawia, że wszystko się dzieje.
Jedna z ankiet przeprowadzonych na Stack-overflow wykazała, że Javascript ma największą liczbę repozytoriów na GitHub. Każdego roku opracowywane są nowe frameworki i biblioteki w JavaScript. Język naprawdę zmienił internet i uprościł życie ludzi.
Tło
Dwa lata temu pracowałem nad pobocznym projektem i po raz pierwszy użyłem Javascript. Będąc doświadczonym programistą od ostatniej dekady, dobrze zapoznałem się ze składnią i rozwinąłem dobre polecenie w ciągu trzech miesięcy.
W miarę upływu miesięcy strona internetowa, którą opracowałem, nie działała dobrze. Załadowanie zajęło dużo czasu i zaczęło wpływać na klientów. Zrobiłem więc głębokie nurkowanie, aby zidentyfikować pierwotną przyczynę.
Po nieprzespanych nocach w końcu byłem na skraju wytrzymałości i byłem bliski poddania się. Postanowiłem jednak spróbować po raz ostatni. Wreszcie pomyślałem o dogłębnym zrozumieniu wewnętrznego działania Javascript. Odesłałem wiele zasobów i w końcu byłem w stanie naprawić błąd w moim kodzie w ciągu dwóch dni.
Inżynierowie na ogół koncentrują się na budowaniu nowych rzeczy. Narzędzia i frameworki, których używamy, ukrywają przed nami większość złożoności. W naszej podróży często zapominamy, jak rzeczy naprawdę działają za kulisami. Niezbędna jest znajomość wewnętrznego działania każdego narzędzia, którego używamy. Dobre zrozumienie wewnętrznych elementów pomaga nam zawęzić problem i znaleźć pragmatyczne rozwiązanie.
W tym artykule przyjrzymy się, co dzieje się za kulisami, gdy JavaScript jest wykonywany przez przeglądarkę. Przyjrzymy się silnikowi JavaScript i zrozumiemy jego działanie. Zobaczymy różne komponenty zaangażowane w środowisko uruchomieniowe JavaScript i uzyskamy jasność co do pętli zdarzeń i kolejki zdarzeń. Chociaż te terminy mogą być dla Ciebie nowe, w artykule postaramy się je uprościć. Zacznijmy od naszego dążenia do opanowania wewnętrznych elementów Javascript.
JavaScript
JavaScript to język interpretowany wysokiego poziomu. W przeciwieństwie do C lub C++, nie musimy kompilować kodu Javascript, a następnie go uruchamiać. Kod JavaScript jest najpierw kompilowany do kodu bajtowego, a następnie tłumaczony na kod na poziomie maszyny.
Wysokie języki, takie jak Java, C, C++, C# obsługują wielowątkowość. Języki te umożliwiają programistom tworzenie wielu wątków i uruchamianie zadań w różnych wątkach. W ten sposób możemy tworzyć zadania na wielu procesorach i efektywnie wykorzystywać zasoby obliczeniowe.
Z drugiej strony Javascript jest jednowątkowy. Oznacza to, że w danym momencie istnieje tylko jeden wątek wykonania. W przypadku wykonania funkcji javascript zajmującej czas, strona internetowa się zawiesi. Być może widziałeś błąd , że witryna nie odpowiada wiele razy podczas przeglądania.
Zróbmy szybkie demo i zobaczmy działanie w akcji. Otworzyłem Twittera i mogę klikać poszczególne zakładki, takie jak Trending, News, Sportsitp.
Teraz w developerkonsoli piszę funkcję zawierającą nieskończoną pętlę. Poniżej znajduje się definicja funkcji.
function infiniteLoop() {
for(i=0; i < 100000; ) {
}
}
infiniteLoop()
Poniższy GIF pokazuje, że nie mogę zmienić karty na Sportslub Entertaintment.
Ponieważ funkcja zawiera nieskończoną pętlę, będzie wykonywana w nieskończoność. Kiedy wybieram różne zakładki, przeglądarka ma obsłużyć clickzdarzenie i odpowiednio wyświetlić zawartość klikniętej zakładki. Nie mogę wybrać żadnej innej karty, ponieważ JavaScript już uruchamia tę funkcję infiniteLoop.
Jak widać w powyższym przykładzie, w JavaScript możemy uruchamiać tylko jedną funkcję na raz. Teraz możesz zapytać, w jaki sposób przeglądarka jest w stanie załadować złożone strony internetowe w ułamku sekundy.
W kolejnych sekcjach zrozumiemy, jak JavaScript robi tę magię za kulisami. Przyjrzyjmy się teraz rdzeniu Javascript, tj. silnikowi Javascript.
Silnik Javascript
Każda przeglądarka zapewnia własny silnik JavaScript. Na przykład: - Chrome używa silnika V8, podczas gdy Firefox używa silnika SpiderMonkey. Silnik jest odpowiedzialny za wykonywanie kodu Javascript linia po linii. Poniżej znajduje się lista popularnych przeglądarek i ich silników JavaScript.
Silnik JavaScript składa się z Heap i Stack . Spróbujmy zrozumieć działanie tych dwóch elementów.
Sterta
Sterta jest odpowiedzialna za przechowywanie definicji funkcji, zmiennych obiektowych . Zawiera wszystkie obiekty, do których odwołuje się kod.
Obiekty są tworzone dynamicznie w stercie. Działanie jest podobne do Java. Ilekroć do obiektu odwołuje się funkcja, jest on pobierany ze sterty.
function bar() {
var player = { "firstName": "Shubhman",
"lastName": "Gill"
}
var teams = ["India", "NewZealand"]
i =0
a = 10
console.log("In the function bar")
}
bar()
Objects on Heap
Stos
Stos to struktura danych typu Last In First Out (LIFO) . Za każdym razem, gdy funkcja jest uruchamiana, jej zmienne lokalne są umieszczane na stosie. Gdy funkcja zakończy wykonywanie, jest usuwana ze stosu. Jeśli funkcja wywoła nową funkcję podczas wykonywania, nowa funkcja również zostanie umieszczona na stosie. Silnik uruchamia funkcję, która znajduje się na szczycie stosu.
Funkcje znajdujące się na dole stosu wznawiają swoje wykonywanie, gdy wszystkie funkcje znajdujące się powyżej stosu powrócą. W przypadku, gdy funkcja zgłasza błąd, ślad stosu jest drukowany na pliku console. Ślad stosu składa się z łańcucha funkcji, które zostały wywołane w momencie zgłoszenia wyjątku. Spójrzmy na przykład.
Zamierzam uruchomić powyższy kod w mojej przeglądarce. I dodaj punkt przerwania w funkcji city.
Jak widać powyżej, podczas wykonywania skryptu zatrzymał się on w punkcie przerwania w funkcji city. Po prawej stronie, w Call Stacksekcji, możesz zobaczyć cityfunkcję u góry.
Składa Call Stacksię ze wszystkich dotychczas wywołanych funkcji. A najnowsza funkcja jest umieszczona na górze. Gdy funkcja zakończy wykonywanie, jest usuwana ze stosu.
Na poniższym obrazku widać, że funkcja cityjest usuwana, a wykonywanie jest kontynuowane dla funkcji streetw wierszu numer 9.
Jak widać powyżej, Javascript może wykonywać tylko jedną funkcję na raz i jest tylko jeden wątek wykonania. Nie można jednocześnie uruchamiać wielu funkcji na stosie wykonawczym.
Załóżmy, że mamy funkcję, która wykonuje wywołanie HTTPi pobiera dane z innej witryny. Co by się stało, gdyby serwer działał wolno, a wykonanie funkcji zajmowało dużo czasu? Nie będziemy mogli uruchomić żadnej innej funkcji, a doświadczenie użytkownika byłoby bardzo słabe.
Czy to się dzieje w realnym świecie? Nie, złożone strony internetowe, takie jak Facebook.com , Twitter.com , Amazon.com itp., uruchamiają mnóstwo kodu Javascript bez wpływu na wygodę użytkownika. Aby zrozumieć, jak to zrobić, zagłębimy się w środowisko wykonawcze Javascript.
Środowisko uruchomieniowe JavaScript
Javascript udostępnia nam różne interfejsy API do pobierania danych z serwera, manipulowania DOM lub wykonywania funkcji za pomocą timera. Ze względu na jednowątkowy charakter Javascript może zatrzymać renderowanie strony internetowej, co prowadzi do niskiej wydajności. Jednak środowisko uruchomieniowe Javascript gwarantuje, że tak się nie stanie. Zrozummy to na przykładzie.
Możesz zgadnąć, jaki będzie wynik poniższego kodu: -
Zostanie getSubTitleswydrukowany po wydrukowaniu getActors? A po wywołaniu setTimeoutkod się zatrzyma?
Poniżej przedstawiono dane wyjściowe wygenerowane przez uruchomienie powyższego kodu: -
Jak widać powyżej, getSubTitleszostał wydrukowany jako pierwszy, a 4 sekundy później getActorszostał wydrukowany. Wprawdzie funkcja setTimeoutczekała 4 sekundy, ale mimo to wcześniej wykonała kolejną funkcję getSubTitlesi kod nie czekał. Teraz zrozumiemy, co działo się za kulisami.
Interfejsy API sieci Web
Nie znajdziesz implementacji funkcji takich jak setTimeout, ani fetchnigdzie w kodzie źródłowym Javascript. Te funkcje są dostarczane przez przeglądarkę i nie są częścią Javascript. Implementacja byłaby inna dla każdej przeglądarki.
Przeglądarka zapewnia funkcje do wykonywania połączeń sieciowych, manipulowania DOM, przetwarzania audio, wideo itp. Funkcje te tworzą interfejsy API sieci Web. Wewnętrzna implementacja jest złożona i składa się głównie z kodu C++. API zapewniają nam interfejs, który można wywołać z kodu Javascript.
Kiedy wywołujesz funkcję, taką jak setTimeout, Javascript rozpoznaje, że jest to Web API. Następnie deleguje wykonanie tej funkcji do kontenera Web API. Funkcja setTimeoutjest wykonywana w osobnym wątku. Nie jest wykonywany w wątku wykonania JavaScript. To jest powód, dla którego kod napisany po setTimeoutzostaje wykonany przed przekazaniem do niego funkcji wywołania zwrotnego.
Podobnie, ponieważ połączenia sieciowe są czasochłonne, funkcje takie jak fetchsą wykonywane w osobnym wątku. Interfejsy API sieci Web przeglądarki ukrywają przed nami większość złożoności. Na przykład: - wewnętrzna implementacja fetchobejmowałaby utworzenie połączenia TCP, wysłanie danych i ich przeanalizowanie. Ponadto wydajnie wykorzystywałby zasoby obliczeniowe, planując wykonanie w odpowiednim wątku.
Poniższy gif pokazuje, co się dzieje, gdy wykonujesz funkcje Web API, takie jak setTimeout.
Jak widać powyżej, po setTimeoutwywołaniu funkcji wysyła ona funkcję wywołania zwrotnego getActorsdo interfejsów API sieci Web. W kontenerze interfejsów API sieci Web funkcja setTimeoutczeka przez cztery sekundy. W międzyczasie getSubTitleswywoływana jest następna funkcja.
Teraz możesz zapytać, w jaki sposób funkcja wywołania zwrotnego jest getActorswykonywana po zakończeniu timera. W następnej sekcji zobaczymy, jak wywołania zwrotne są wykonywane po powrocie Web API.
Kolejka zdarzeń
JavaScript w dużym stopniu opiera się na funkcjach wywołania zwrotnego. Funkcje wywołania zwrotnego są wykonywane po wystąpieniu zdarzenia. Np.: - Po otrzymaniu odpowiedzi z serwera musimy wydrukować reprezentację json. Lub wykonaj funkcję z opóźnieniem 4 sekund.
Środowisko wykonawcze Javascript składa się z komponentu znanego jako Kolejka zdarzeń lub Kolejka wywołań zwrotnych. Jest to kolejka komunikatów i używa kolejności FIFO (First In First Out). Komunikaty to nic innego jak funkcje wywołania zwrotnego zaplanowane do wykonania.
Weźmy przykład, aby to zrozumieć. W poniższym kodzie rejestrujemy funkcję wywołania zwrotnego za każdym razem, gdy wystąpi zdarzenie kliknięcia przycisku. Gdy użytkownik kliknie przycisk, funkcja onClickzostanie wywołana. Ta funkcja wywołuje funkcję timerz opóźnieniem 2 sekund.
Powyższa animacja pokazuje, co dzieje się za kulisami. Mamy tutaj dwa interfejsy API sieci Web — a) $.oni b) setTimeout. Pierwszy interfejs API czeka na zdarzenie kliknięcia. Gdy otrzyma zdarzenie kliknięcia, wykona funkcję onClick. Drugi interfejs API setTimeOutjest następnie wywoływany przez onClickfunkcję.
Za każdym razem, gdy użytkownik kliknie przycisk, funkcja onClickjest umieszczana w kolejce zdarzeń. Podobnie, po zakończeniu odmierzania czasu, funkcja timerzostaje umieszczona w kolejce. Tak więc kolejka zdarzeń składa się ze wszystkich funkcji wywołania zwrotnego, które muszą zostać wykonane. W tym przypadku onClicki timerfunkcji.
Pętla zdarzeń
Funkcje wywołania zwrotnego EventQueuedocierają do stosu wykonawczego Javascript poprzez pętlę zdarzeń. Pętla zdarzeń stale sprawdza, czy bieżący stos JavaScript jest pusty. Jeśli stwierdzi, że stos jest pusty, wybiera pierwszą wiadomość z kolejki zdarzeń i przesuwa ją na stos.
Biorąc przykład z poprzedniej sekcji, gdy tylko pętla zdarzeń stwierdzi, że stos jest pusty, pobierze następny komunikat z kolejki wywołań zwrotnych i wykona go.
Poniżej znajduje się zgrubny pseudokod działania pętli zdarzeń: -
Pętlę zdarzeń można traktować jako nieskończoną pętlę, która stale monitoruje stos wykonania. W przypadku, gdy stos jest pusty, pobierze wiadomość (funkcję) z kolejki zdarzeń i wykona ją.
Poniższy diagram przedstawia wysokopoziomową reprezentację środowiska uruchomieniowego JavaScript.
Przyjrzeliśmy się różnym komponentom środowiska uruchomieniowego Javascript. Teraz będziesz miał lepsze wyobrażenie o tym, jak JavaScript działa pod maską. Podsumujmy to, czego dowiedzieliśmy się do tej pory.
Streszczenie
- JavaScript jest jednowątkowy i wykonuje jedną funkcję na raz.
- Wykorzystuje interfejsy API sieci Web do wykonywania złożonych zadań w tle.
- Po zakończeniu wykonywania Web API wysyła funkcje wywołania zwrotnego do kolejki zdarzeń.
- Pętla zdarzeń stale sprawdza, czy stos JavaScript jest pusty. Jeśli stos jest pusty, pobiera funkcję wywołania zwrotnego z kolejki zdarzeń i uruchamia ją.
- Elementy wewnętrzne i środowisko wykonawcze Javascript
- Jak działa Javascript w przeglądarce i node
- Animowany przewodnik po pętli zdarzeń Node.js
- Co to do cholery jest pętla zdarzeń?
- Asynchroniczny Javascript i pętla zdarzeń od podstaw

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



































