JavaScript nigdy nie miał stać się tym, czym jest dzisiaj. W 1995 roku Brendan Eich usiadł w Netscape i stworzył prototyp w dziesięć dni. Dziesięć dni. To ledwie wystarczający czas na napisanie porządnej specyfikacji, a co dopiero zaprojektowanie języka programowania przeznaczonego do obsługi miliardów urządzeń. Wynikiem był język pełen dziwactw, na których programiści wciąż się potykają niemal trzy dekady później. Mimo to ta sama pośpieszna kreacja stała się najszerzej wdrażanym środowiskiem uruchomieniowym w historii oprogramowania. Odniósł sukces nie dlatego, że był elegancki, ale dlatego, że trafił do przeglądarek dokładnie w momencie, gdy sieć tego potrzebowała.

Narodzony w pośpiechu

Wojny przeglądarek w połowie lat dziewięćdziesiątych nie były przyjazną rywalizacją. Netscape potrzebował lekkiego języka skryptowego, który mógłby działać obok Javy w przeglądarce Navigator. Eich otrzymał zadanie zbudowania czegoś, co przypominałoby Javę na tyle, by uspokoić kadrę zarządzającą, ale jednocześnie było wystarczająco proste, by osoby niebędące programistami mogły wklejać je do stron internetowych. Termin był absurdalny. Stworzył Mocha, którą wkrótce przemianowano na LiveScript, a ostatecznie na JavaScript, co było zagrywką marketingową mającą wykorzystać popularność Javy.

Ten pośpieszny początek pozostawił trwałe blizny. Konwersja typów wciąż dezorientuje nowicjuszy, gdy operator plus łączy ciągi znaków i liczby bez ostrzeżenia. typeof null zwraca "object" z powodu błędu w oryginalnej implementacji, którego nikt nie odważy się naprawić w obawie przed zepsuciem sieci. Automatyczne wstawianie średników (automatic semicolon insertion) powoduje ciche błędy. Zmienne zadeklarowane za pomocą var wyciekają w zakresie (scope) w sposób, który wydaje się nieprzewidywalny. To nie są abstrakcyjne wady projektowe. To codzienne frustracje, które wywodzą się bezpośrednio z dwutygodniowego sprintu w maju 1995 roku.

Dziwne pochodzenie

Przyjrzyj się uważnie JavaScriptowi, a zobaczysz trzy odrębne linie rodowe splecione ze sobą. Składnia mocno zapożycza z Javy. Nawiasy klamrowe, instrukcje if i pętle for nadają znajomy kształt każdemu, kto przychodzi ze światów języków typu C. Jednak pod tą powierzchnią zachowanie jest zupełnie inne.

Prawdziwe obliczeniowe serce języka pochodzi ze Scheme, dialektu Lispu. To właśnie stąd JavaScript zaczerpnął funkcje pierwszej klasy (first-class functions), co oznacza, że funkcje mogą być przekazywane jako argumenty, zwracane z innych funkcji i przypisywane do zmiennych. Dało nam to również domknięcia (closures), które pozwalają funkcji wewnętrznej na dostęp do zakresu funkcji zewnętrznej, nawet po zakończeniu wykonywania tej zewnętrznej. Jeśli kiedykolwiek pisałeś callback lub dodawałeś listener zdarzeń, korzystałeś z DNA odziedziczonego po Scheme.

Jest jeszcze model obiektowy, który pochodzi z języka Self. Zamiast klasycznej dziedziczności z sztywnymi klasami, JavaScript używa prototypów. Obiekt może łączyć się bezpośrednio z innym obiektem i delegować wyszukiwanie właściwości w górę. Możesz stworzyć obiekt za pomocą Object.create i budować łańcuchy bez definiowania klasy. Nowoczesny JavaScript dodał słowo kluczowe class, ale jest to głównie „cukier składniowy” (syntactic sugar) nad tą podstawową mechaniką prototypów.

Od dekoracji stron do poważnego narzędzia

Przez pierwsze lata JavaScript wykonywał drobne zadania. Walidował dane w formularzach przed wysłaniem ich na serwer. Podmieniał obrazy po najechaniu myszką. Był zabawką, a nie narzędziem. Implementacje w przeglądarkach były niespójne, więc programiści często pisali różne ścieżki kodu dla Netscape i Internet Explorera.

Standaryzacja poprzez ECMAScript zmieniła ten bieg zdarzeń. Specyfikacja dała producentom przeglądarek wspólny cel implementacji, co powoli wyeliminowało najgorsze niekompatybilności. Potem pojawił się Ajax.

Ajax, skrót od Asynchronous JavaScript and XML, nie był pojedynczą nową technologią, lecz wzorcem łączącym istniejące elementy. Kluczowym składnikiem był obiekt XMLHttpRequest, który pozwalał przeglądarce prosić serwer o dane w tle bez przeładowywania całej strony. Gdy Google uruchomiło Maps w 2005 roku i Gmail w 2004 roku, użytkownicy nagle doświadczyli responsywności przypominającej aplikacje desktopowe wewnątrz karty przeglądarki. Strony internetowe stały się aplikacjami. JavaScript nie był już tylko ozdobnikiem. Stał się daniem głównym.

Szybkość i ambicja

Czysta wydajność była niegdyś największym żartem JavaScriptu. Wczesne interpretery były wolne. Potem Google wydało silnik V8 w 2008 roku wraz z Chrome, a żart przestał być śmieszny. V8 wprowadził kompilację JIT (just-in-time), tłumaczącą JavaScript na kod maszynowy w czasie wykonywania, zamiast interpretować go linijka po linijce. Wprowadził ukryte klasy (hidden classes) i cachowanie w linii (inline caching), aby dostęp do właściwości był szybki nawet w przypadku obiektów dynamicznych. Inne przeglądarki odpowiedziały własnymi silnikami wysokiej prędkości, a język nagle stał się wystarczająco szybki do rzeczywistych obliczeń.

Ta prędkość umożliwiła kolejną zmianę. Ryan Dahl wydał Node.js w 2009 roku, wyrywając V8 z przeglądarki i opakowując go w model wejścia-wyjścia typu non-blocking, oparty na zdarzeniach. Serwery WWW kiedyś tworzyły nowy wątek dla każdego przychodzącego żądania, co prowadziło do awarii przy dużym współbieżnym obciążeniu. Node.js obsługiwał dziesiątki tysięcy jednoczesnych połączeń na pojedynczym wątku, wykorzystując pętlę zdarzeń (event loop) i asynchroniczne callbacki. JavaScript wypłynął poza klienta i trafił na serwer, do narzędzi budujących, a ostatecznie do wszystkiego innego.

Język obecny wszędzie

Teraz JavaScript działa w miejscach, których jego twórca nigdy nie mógł sobie wyobrazić.

Na frontendzie React i Vue kształtują sposób budowania nowoczesnych interfejsów. Komponenty aktualizują się w odpowiedzi na zmiany stanu bez konieczności kosztownego, pełnego przeładowywania strony przez przeglądarkę. Na backendzie Node.js napędza API i usługi czasu rzeczywistego, podczas gdy nowsze środowiska uruchomieniowe, takie jak Bun, eksperymentują z szybszym zarządzaniem pakietami i wbudowanym bundlingiem.

React Native tłumaczy kod JavaScript na natywne widoki platformy, co pozwala zespołom wydawać aplikacje mobilne na iOS i Android bez konieczności utrzymywania dwóch całkowicie oddzielnych baz kodu w Swift i Kotlin. Electron opakowuje technologie webowe w powłoce Chromium, aby budować oprogramowanie desktopowe – tak właśnie Slack i Visual Studio Code trafiają na Twój laptop. Język ten znajduje się nawet wewnątrz funkcji chmurowych i workerów edge computing, wykonując logikę w odległości milisekund od użytkownika końcowego w rozproszonych sieciach.

Paradoks obfitości

Powszechność ma swoją cenę. Ekosystem jest ogromny, a ta skala rodzi niepokój. Nowe narzędzie budujące pojawia się, zanim zdążysz skonfigurować poprzednie. Popularność frameworków rośnie i spada w cyklach, które wydają się sezonowe. Możesz rozwiązać niemal każdy problem, nie opuszczając świata JavaScript, ale najpierw musisz wybrać spośród tuzina rozwiązań o bardzo konkretnych założeniach. Drzewa zależności stają się głębokie i kruche. Pakiet do lewego dopełniania tablic (left-padding) może zepsuć tysiące projektów zależnych, gdy zniknie z rejestru.

Nic z tego nie jest przypadkowe. JavaScript jest chaotyczny, ponieważ sieć jest chaotyczna. To organiczny, wielowarstwowy tort złożony z kompatybilności wstecznej, pośpiesznych standardów i rywalizujących interesów implementacyjnych. Jednak ten sam chaos sprawia, że język jest potężny. Sieć jest wszędzie, a ponieważ JavaScript domyślnie znajduje się w każdej przeglądarce, jest najbliższą rzeczą, jaką mamy, uniwersalnym środowiskiem uruchomieniowym.

Przestał być zwykłym językiem skryptowym już dawno temu. JavaScript jest obecnie globalną platformą dla oprogramowania, zbudowaną w dziesięć dni, trzymaną razem przez niewidzialny kontrakt, zgodnie z którym sieć nie może zepsuć tego, co powstało wcześniej. Poznając jego blizny wraz z jego mocnymi stronami, poznajesz historię samego nowoczesnego internetu.