JavaScript to skryptowy język programowania, który służy do tworzenia interaktywnych i ruchomych elementów na stronach internetowych. Przeglądarki interpretują go w czasie rzeczywistym, co pozwala na modyfikowanie struktury HTML i stylów CSS bez konieczności przeładowywania widoku. Prawda jest zresztą absolutnie taka, że bez tego języka współczesny internet byłby zbiorem statycznych, martwych dokumentów tekstowych odesłanych wprost z lat dziewięćdziesiątych.
Aplikacje webowe opierają się na nim w niemal stu procentach. Kiedy klikasz przycisk „Kup teraz”, kiedy przewijasz nieskończoną listę postów na portalu społecznościowym, albo gdy wypełniasz formularz, który od razu podświetla błędy w adresie e-mail. To wszystko robi JavaScript. Wykonuje się bezpośrednio na twoim komputerze lub telefonie. Zdejmuje obciążenie z serwerów.
Do czego służy JavaScript w codziennej pracy programisty?
Głównym zadaniem tego języka jest obróbka zdarzeń. Użytkownik rusza myszką, naciska klawisz, dotyka ekranu. Przeglądarka rejestruje ten fakt i przekazuje go do skryptu. Kod decyduje, co z tym faktem zrobić. Wyświetla okno pop-up. Wysyła dane w tle za pomocą zapytań asynchronicznych. Zmienia kolor tła.
Widzimy to na każdym kroku. Programiści front-end piszą w nim logikę interfejsu. Tworzą zaawansowane panele administracyjne, systemy rezerwacji biletów i gry przeglądarkowe. Ale język ten dawno uciekł z klatki, jaką była przeglądarka internetowa. Dzięki środowisku uruchomieniowemu Node.js, JavaScript wykonuje operacje na serwerze. Łączy się z bazami danych. Czyta i zapisuje pliki na dysku twardym maszyny. Tworzy pełnoprawne interfejsy API.
(Prawda jest zresztą absolutnie taka, że kiedy we wtorek o trzeciej nad ranem próbujesz zdiagnozować wyciek pamięci w starym systemie napisanym w czystym JS, wiesz jedno. Żadne akademickie teorie o czystości kodu nie mają znaczenia. Siedzisz, patrzysz w stos wywołań na monitorze. Zmęczenie zżera oczy. Masz ochotę rzucić to wszystko i wyjechać. Zmieniasz jedną zmienną globalną na lokalną. Wykres zrzutu pamięci nagle spada. Działa. I to jest ten moment, kiedy rozumiesz, że ten język wybacza dużo, ale potrafi też boleśnie ukarać za ignorancję).
Zastanawiacie się zresztą, dlaczego wasza aplikacja firmowa działa wolno na telefonach? Sam się nad tym borykałem dzisiaj u siebie we wtorek na testach produkcyjnych. Ktoś załadował czternaście megabajtów nieskompresowanego kodu bibliotek tylko po to, żeby wyświetlić prosty kalendarz urlopów w małym urzędzie gminy na Śląsku. Taka jest cena łatwości użycia. Kiedy próg wejścia w technologię jest niski, jakość końcowego produktu zależy wyłącznie od dyscypliny człowieka siedzącego przed klawiaturą.
Czym dokładnie jest manipulacja strukturą DOM?
DOM, czyli Document Object Model, to drzewo reprezentujące całą strukturę strony internetowej. Przeglądarka czyta plik HTML i buduje z niego hierarchiczną mapę węzłów. JavaScript wchodzi do tej struktury i robi w niej porządki. Dodaje nowe gałęzie. Usuwa stare. Zmienia atrybuty istniejących elementów. To najcięższa i najbardziej kosztowna operacja dla procesora.
Wielu początkujących deweloperów używa metody innerHTML by zastąpić zawartość całych sekcji strony. To błąd. Odtwarzanie całego drzewa dla jednej małej zmiany zżera zasoby. Właśnie dlatego powstały narzędzia takie jak wirtualny DOM. Mechanizm ten trzyma kopię struktury w pamięci RAM, porównuje zmiany i aktualizuje tylko ten jeden, wyselekcjonowany piksel na ekranie, który faktycznie wymaga odrysowania.
- Znajdowanie elementu po identyfikatorze to najszybsza operacja. Funkcja getElementById uderza prosto w cel i zwraca referencję w ułamku milisekundy, co przy dużych serwisach ratuje płynność animacji podczas przewijania ekranu przez użytkownika na starych urządzeniach z Androidem.
- Używanie selektorów CSS przez querySelector zmusza silnik do dłuższego parsowania.
- Każda zmiana klasy CSS na elemencie wywołuje proces reflow.
- Nasłuchiwanie zdarzeń na setkach małych przycisków zabija wydajność
Chociaż prawdę mówiąc brakuje nam twardych danych za wczoraj z testów wydajnościowych, więc wydaje się to tylko jedną z możliwych hipotez na spadki FPS-ów w przeglądarkach mobilnych. Zmieniliśmy te zasady na robocie. Teraz podpinamy jeden nasłuchiwacz na główny kontener i sprawdzamy, w co dokładnie kliknął użytkownik. Nazywa się to delegacją zdarzeń. Prosta sztuczka. Ratuje życie.
Dlaczego front-end i back-end używają teraz tej samej technologii?
Historycznie podział był prosty. Java, PHP lub Ruby rządziły na serwerze. JavaScript pilnował przycisków w przeglądarce. Potem pojawił się Ryan Dahl i wziął silnik V8 z Google Chrome. Wyprowadził go na zewnątrz. Stworzył Node.js. Nagle okazało się, że programiści znający tylko jeden język mogą budować aplikacje od bazy danych aż po widok w oknie klienta.
To obniżyło koszty zatrudnienia. Firmy przestały szukać dwóch osobnych zespołów. Zaczęto promować stanowisko Full-Stack Developera. Utrzymanie jednego języka w całym stosie technologicznym oznacza, że możemy współdzielić kod. Modele walidacji danych napisane do sprawdzania formularza w przeglądarce są importowane bezpośrednio do plików serwerowych, by pilnować bezpieczeństwa bazy danych. Jedna zmiana w regułach biznesowych aplikuje się wszędzie natychmiast.
| Cecha środowiska | JavaScript w przeglądarce (Client-side) | JavaScript na serwerze (Node.js) | Główne wyzwania i problemy |
| Dostęp do systemu | Brak dostępu do plików lokalnych ze względów bezpieczeństwa. | Pełny dostęp do dysku twardego, procesów i sieci. | Ryzyko wykonania obcego kodu. |
| Zarządzanie oknem | Posiada obiekty window i document do sterowania widokiem. | Brak interfejsu graficznego. Zwraca czyste dane lub błędy. | Brak spójności w nazwach zmiennych globalnych. |
| Asynchroniczność | Czeka na kliknięcia i reakcje człowieka. | Czeka na zapytania sieciowe i odczyty z bazy danych. | Słynne piekło wywołań zwrotnych (callback hell). |
| Wydajność | Zależy od procesora w telefonie klienta. | Zależy od parametrów serwera w chmurze i skalowania. | Blokowanie jedynego wątku operacyjnego. |
A to rodzi swoje problemy. Node.js działa na jednym wątku. Jeśli napiszesz ciężki, matematyczny algorytm do obliczania statystyk i uruchomisz go na głównym wątku serwera, zablokujesz wszystkich innych użytkowników próbujących się zalogować. Oczekiwanie na odpowiedź z bazy danych zlagowało nam kiedyś cały system autoryzacji na piętnaście minut w czwartek popołudniu. Wtedy uczysz się, że asynchroniczność to nie jest opcja. To bezwzględny wymóg.
Jak silnik V8 kompiluje kod w czasie rzeczywistym?
Przeglądarki nie czytają tekstu pisanego przez programistę bezpośrednio. Kod źródłowy musi zostać zamieniony na kod maszynowy, zrozumiały dla procesora. Silnik V8 od Google robi to z użyciem kompilacji JIT (Just-In-Time). Nie kompiluje całego programu z góry, jak robi to C++. Analizuje tekst w locie, w trakcie działania aplikacji na ekranie.
V8 najpierw zamienia tekst na tak zwane drzewo składniowe (AST). Potem szybki kompilator Ignition generuje z tego niewydajny kod bajtowy, by strona uruchomiła się jak najszybciej. W tle silnik obserwuje, które funkcje są wywoływane najczęściej. Te fragmenty kodu oznacza jako gorące. Wysyła je do drugiego, zaawansowanego kompilatora o nazwie TurboFan. Ten optymalizuje je pod konkretny procesor, wymieniając wolny kod bajtowy na błyskawiczne instrukcje maszynowe.
Tyle. Brzmi prosto na papierze. W rzeczywistości to potężny system próbujący zgadnąć intencje programisty. Jeżeli napiszesz funkcję, która raz przyjmuje liczbę, a za chwilę rzucisz do niej łańcuch tekstu, TurboFan dostanie szału. Wyrzuci zoptymalizowany kod do kosza i wróci do wolnej wersji. Nazywa się to deoptymalizacją. To główny powód, dla którego wprowadzono język TypeScript. Wymusza on sztywne typy danych, by silniki przeglądarek nie musiały zgadywać, co zaraz wpadnie do zmiennej w pamięci RAM.
Co to jest Event Loop i dlaczego wątek główny się blokuje?
JavaScript jest jednowątkowy. Ma tylko jeden stos wywołań (Call Stack). Może robić tylko jedną rzecz naraz. A jednak pobiera gigabajty danych z sieci, odtwarza wideo i obsługuje animacje w tym samym czasie. Jak to możliwe? Używa pętli zdarzeń (Event Loop) i interfejsów dostarczanych przez przeglądarkę.
Kiedy wywołujesz funkcję pobierającą dane z serwera (na przykład fetch), silnik JavaScript nie czeka na odpowiedź. Przekazuje to zadanie do Web API przeglądarki i idzie robić inne rzeczy. Rysuje przyciski. Odbiera ruchy myszką. Kiedy dane z serwera w końcu przylecą, Web API wrzuca funkcję zwrotną do kolejki (Callback Queue). Pętla zdarzeń cały czas patrzy na stos wywołań. Kiedy stos jest całkowicie pusty, Event Loop bierze pierwszą rzecz z kolejki i wrzuca ją na stos do wykonania.
Zrozumienie tego mechanizmu oddziela amatorów od ludzi, którzy wiedzą co robią przy produkcji. Wywołanie funkcji setTimeout z czasem ustawionym na zero milisekund wcale nie oznacza, że kod wykona się natychmiast. Oznacza to, że wykona się w najbliższym możliwym momencie, kiedy główny wątek będzie wolny. Często to różnica kilku klatek animacji, która decyduje o tym, czy interfejs sprawia wrażenie responsywnego, czy zacina się przy każdym dotknięciu ekranu.
Jakie środowiska pracy budują nowoczesne aplikacje webowe?
Czysty język programowania to dzisiaj za mało, by dowieźć duży projekt w terminie narzuconym przez zarząd. Korzystamy z potężnych gotowców organizujących architekturę plików. Biblioteki i frameworki narzucają konkretny styl pisania. Wymuszają porządek. Standaryzują nazewnictwo, by nowy pracownik mógł wejść do projektu i pierwszego dnia zrozumieć, gdzie leży logika logowania, a gdzie pliki od wyglądu koszyka zakupowego.
Na rynku dominują obecnie dwa główne podejścia do budowania interfejsów.
React, stworzony przez Meta (dawniej Facebook), to biblioteka skupiona wyłącznie na widoku. Opiera się na architekturze komponentów. Budujesz małe klocki. Przycisk. Pole tekstowe. Nagłówek. Potem składasz z nich całą stronę. React używa składni JSX, która pozwala pisać kod przypominający HTML bezpośrednio wewnątrz plików JavaScript. To podejście budziło na początku kontrowersje, ale dzisiaj to standard branżowy u zdecydowanej większości deweloperów. Wymaga jednak dobrania zewnętrznych narzędzi do routingu czy zarządzania stanem globalnym aplikacji.
Angular, wspierany przez Google, to kombajn. Przynosi ze sobą wszystko. Od klienta HTTP, przez zaawansowany system formularzy, aż po rygorystyczne wstrzykiwanie zależności (Dependency Injection). Angular od samego zarania wymusza używanie TypeScriptu. Kod jest podzielony na klasy i dekoratory. Z tego powodu korporacje kochają to narzędzie. Daje poczucie bezpieczeństwa w projektach, nad którymi pracuje kilkadziesiąt osób rozsianych po różnych krajach.
Vue.js oraz Svelte zdobywają uznanie mniejszych zespołów. Svelte w ogóle rezygnuje z wirtualnego DOM-u. Przenosi ciężar pracy z przeglądarki użytkownika na proces budowania aplikacji (build time). Kompiluje kod komponentów do czystych, małych instrukcji zmieniających interfejs. Aplikacje w Svelte ważą ułamek tego, co wypluwa z siebie React. To potężna przewaga na rynkach, gdzie użytkownicy korzystają ze słabych smartfonów i wolnych połączeń komórkowych na obrzeżach miast.
Czy standardy ECMAScript nadążają za rynkiem?
Język musi ewoluować. Za rozwój specyfikacji odpowiada komitet TC39 w organizacji ECMA International. Specyfikacja nosi nazwę ECMAScript, a JavaScript jest po prostu jej najpopularniejszą implementacją. Kiedyś na nowości czekaliśmy latami. Edycja szósta (ES6) z 2015 roku wywróciła wszystko do góry nogami. Wprowadziła zmienne blokowe let i const, funkcje strzałkowe, klasy i system modułów import/export.
Obecnie komitet wydaje aktualizacje co roku. Proces jest jawny. Każdy może zgłosić propozycję nowej funkcji. Propozycja przechodzi przez cztery etapy oceny. Jeśli dotrze do czwartego, trafia do oficjalnego standardu w kolejnym roku. To zabiło problem stagnacji. Ostatnio dodano opcjonalne łańcuchowanie (optional chaining), które oszczędziło nam pisania setek linijek kodu sprawdzającego, czy zagnieżdżony obiekt w ogóle istnieje w pamięci przed odczytaniem jego właściwości.
Jeżeli pobierasz dane o użytkowniku i nie wiesz, czy ma uzupełniony adres, kiedyś pisałeś ify. Teraz piszesz user?.address?.street i masz spokój. Jeśli czegoś brakuje, kod po prostu zwraca wartość undefined i nie rzuca czerwonym błędem krytycznym w konsoli. Taka poprawa jakości życia programisty przekłada się bezpośrednio na mniejszą liczbę zgłoszeń od działu obsługi klienta u spodu łańcucha pokarmowego w firmie.
Asynchroniczność i piekło wywołań zwrotnych
Kiedyś obsługa operacji trwających w czasie opierała się na funkcjach przekazywanych jako argumenty. Zrobiłeś zapytanie do bazy, przekazywałeś funkcję, która miała się wykonać po otrzymaniu danych. A co, jeśli te dane trzeba było znowu wysłać do innej usługi? Przekazywałeś kolejną funkcję. Powstawała piramida zagłady, kod wcięty w prawo na pół monitora. Utrzymanie tego było koszmarem.
NIGDY nie chcemy do tego wracać.
Potem pojawiły się Obietnice (Promises). Obiekt reprezentujący operację, która zakończy się w przyszłości sukcesem lub porażką. Pozwoliło to na płaskie łańcuchowanie metod .then(). Kod stał się czytelniejszy. Wciąż jednak wymagał pisania specyficznego żargonu wewnątrz funkcji wywołujących.
Ostatecznym rozwiązaniem okazała się składnia async/await. Pozwala ona pisać kod asynchroniczny tak, jakby był to stary, dobry, blokujący kod synchroniczny z języków takich jak PHP. Stawiasz słowo await przed wywołaniem zapytania do serwera. Wykonanie funkcji wstrzymuje się w tym konkretnym miejscu, aż serwer odpowie. Wątek główny przeglądarki w tym czasie normalnie obsługuje kliknięcia. Kiedy odpowiedź wraca, funkcja podejmuje pracę w linijce poniżej. To drastycznie zmniejszyło liczbę błędów w logice aplikacji bankowych i e-commerce.
Zarządzanie pamięcią i Garbage Collector
Programiści C wiedzą, jak alokować pamięć i jak ją zwalniać. W językach skryptowych robi to za nas automat. Kiedy deklarujesz obiekt lub tablicę, silnik przeglądarki sam rezerwuje miejsce w pamięci operacyjnej urządzenia. Kiedy przestajesz używać tej zmiennej, automat wkracza do akcji. Śmieciarka (Garbage Collector) skanuje pamięć, szuka obiektów, do których nie ma już żadnych referencji w działającym kodzie, i zwalnia te sektory.
Mechanizm ten opiera się na algorytmie Mark-and-Sweep. Startuje od głównego obiektu window, idzie po wszystkich zmiennych i zaznacza te, do których potrafi dotrzeć. Wszystko to, co pozostało nieoznaczone, jest kasowane. Problem pojawia się, gdy programista przypadkowo zostawi referencję do potężnego obiektu w ukrytym domknięciu (closure) lub w globalnej tablicy nasłuchiwaczy zdarzeń. Pamięć rośnie. Aplikacja zwalnia. Przeglądarka ubija kartę. To częsty scenariusz na urządzeniach mobilnych z małą ilością RAM-u.
Dlatego tak ważne jest sprzątanie po sobie. Jeśli montujesz komponent pokazujący mapę dojazdu, a po chwili użytkownik przechodzi do innej zakładki, musisz ręcznie zabić instancję tej mapy. Musisz usunąć timery. Inaczej zostawiasz w systemie sieroty, które zżerają zasoby baterii w tle. To nasza bezpośrednia odpowiedzialność za sprzęt klienta na biurku.
Jak zacząć naukę tego języka bez frustracji?
Nie od frameworków. To najczęstszy błąd. Ludzie po dwóch dniach czytania o zmiennych rzucają się na instalację Reacta. Nie rozumieją, co robi biblioteka, a co jest wbudowanym mechanizmem samego języka. Zacinają się na pierwszym błędzie kompilacji. Porzucają projekt i idą szukać gotowych rozwiązań na forach.
Zacznij od czystego JavaScriptu (tak zwanego Vanilla JS). Zrozum typy danych. Liczby, ciągi znaków, wartości logiczne boolean. Dowiedz się, dlaczego typ null to obiekt, a undefined to brak przypisanej wartości. To są fundamenty, na których opiera się cała reszta. Naucz się pętli for, operacji na tablicach za pomocą wbudowanych metod map, filter i reduce. Zrozum słowo kluczowe „this”, które w tym języku działa inaczej niż w Javie czy C# i potrafi zmienić swój kontekst w zależności od tego, w jaki sposób wywołano funkcję.
Otwórz konsolę deweloperską w swojej przeglądarce. Naciśnij F12. Wpisz polecenie alert(’Działa’). Zobaczysz, że masz przed sobą w pełni skonfigurowane środowisko programistyczne. Nie musisz instalować ciężkich edytorów, konfigurować ścieżek dostępu ani ustawiać serwerów lokalnych. Przeglądarka ma to wszystko wbudowane. To drastycznie skraca czas od pomysłu do pierwszego widocznego efektu na ekranie.
Zbuduj prosty kalkulator. Zrób listę zadań do zrobienia (To-Do list), która zapisuje dane do LocalStorage przeglądarki, by nie zniknęły po odświeżeniu strony. Pobierz darmowe dane o pogodzie z publicznego API i wyświetl je w tabeli. Kiedy to zadziała, poczujesz mechanikę requestów sieciowych na własnej skórze. Dopiero wtedy dobrym pomysłem jest sięgnięcie po dokumentację nowoczesnego frameworka.
Wyzwanie na koniec
Zostawiam cię z konkretnym poleceniem, zamiast poetyckich rozważań o przyszłości web developmentu. Otwórz teraz dowolną dużą stronę informacyjną. Kliknij prawym przyciskiem myszy, wybierz opcję Zbadaj. Przejdź do zakładki Network i odśwież stronę. Popatrz na listę ładujących się plików z rozszerzeniem .js. Policz, ile megabajtów kodu ściąga się w tle, tylko po to, by wyświetlić ci artykuł z tekstem i trzema zdjęciami. Przeanalizuj czas ładowania tych skryptów na czerwonym pasku błędów. Zobaczysz tam chaos. Zobaczysz krew, pot i łzy ludzi łatących systemy analityczne i reklamowe by zdążyć przed piątkowym wydaniem. Zrozumiesz wtedy, że prawdziwym sprawdzianem dla inżyniera oprogramowania nie jest napisanie kodu, który po prostu działa. Prawdziwym testem jest napisanie kodu, który nie zniszczy urządzenia użytkownika. Tyle. Zrób to od razu. Zobacz, w jakim środowisku przyjdzie ci pracować od jutra.
Najczęściej zadawane pytania (FAQ)
- 1. Co to jest JavaScript w prostych słowach?
-
To język programowania, który dodaje interaktywność do stron internetowych. Odpowiada za logikę, animacje, formularze i pobieranie danych w tle bez przeładowywania widoku okna przeglądarki.
- 2. Do czego służy JavaScript na stronach WWW?
-
Służy do manipulacji strukturą DOM. Programista używa go by modyfikować tekst, zmieniać kolory, chować lub pokazywać elementy interfejsu na podstawie akcji użytkownika na ekranie.
- 3. Czym różni się Java od JavaScriptu?
-
To dwa zupełnie różne języki o różnym przeznaczeniu i składni. Java to ciężki język kompilowany używany głównie w systemach korporacyjnych i aplikacjach na Androida. JS to język skryptowy stworzony z myślą o przeglądarkach internetowych.
- 4. Czy JavaScript działa na serwerze?
-
Działa na serwerach dzięki środowisku Node.js. Pozwala ono uruchamiać kod poza przeglądarką, tworzyć interfejsy API, łączyć się z bazami danych i zarządzać plikami na twardym dysku maszyny.
- 5. Jak zacząć naukę JavaScriptu?
-
Najlepiej od podstawowej składni bez używania gotowych frameworków. Wystarczy edytor kodu i wbudowana konsola w przeglądarce Chrome lub Firefox. Najszybsze efekty daje budowa prostych list zadań i kalkulatorów ucinając wprost teorię bez praktyki.
- 6. Co to jest silnik V8?
-
To otwarty silnik stworzony przez Google. Tłumaczy on kod napisany przez człowieka bezpośrednio na instrukcje maszynowe zrozumiałe dla procesora za pomocą kompilatora JIT, zapewniając bardzo wysoką prędkość działania aplikacji.
Bibliografia
- Mozilla Developer Network – https://developer.mozilla.org
- W3C – https://www.w3.org
- ECMA International – https://www.ecma-international.org
- Node.js Foundation – https://nodejs.org
- Stack Overflow – https://stackoverflow.com
