Wzorce modułu w JavaScript.

W poprzednim poście opisałem JS’owe sposoby na tworzenie nowych obiektów. Tym razem pociągnę temat organizacji kodu i pokażę kilka prostych wzorców dzielenia programu na moduły.

Moduły to nieodzowny element każdej większej aplikacji. Pomagają one utrzymać kod w porządku, oraz często chronią przed błędami wynikającymi z przypadkowego nadpisywania ważnych dla aplikacji wartości.

JavaScript wzorce modułu

Zdarza mi się spotykać z sytuacją, w której ludzie dopiero zaczynający poznawać JavaScript mylą pojęcia klasy i modułu. Na samym początku chciałbym rozwiać wszelkie wątpliwości, moduł nie jest tym samym co klasa. Klasa to obiekt, na podstawie którego tworzy się inne obiekty, dziedziczące właściwości klasy. Zastosowanie modułu jest inne. Często powstaje w podobny sposób co obiekt klasy (zresztą nic dziwnego, w JS wszystko jest obiektem 😉 ), ale na tym podobieństwa się kończą. Podstawową różnicą jest to, że każdy moduł jest jedyny w swoim rodzaju a instancji klas może być bardzo wiele.

Do czego w takim razie potrzebne są moduły? Przede wszystkim, są to sprytne sposoby na to aby radzić sobie z wyciekiem danych do kontekstu globalnego. Wszystkie dane należące do jakiejś logicznej całości umieszczane są wewnątrz modułu. Do tego pewne implementacje modułów pozwalają na całkowite ukrywanie części danych, tak, że nie są dostępne spoza modułu. To wszystko jest naprawdę potrzebne podczas tworzenia dużych aplikacji, takich na które składa się mnóstwo plików i nad którymi pracuje wiele osób. Gdyby każdy przypisywał dane do kontekstu globalnego, bardzo szybko stało by się coś złego.

Myślę, że nic lepiej nie wytłumaczy idei modułów niż przykłady ich implementacji.

Moduł stworzony z literału obiektowego

Użycie literału obiektowego do stworzenia modułu to najprostszy a zarazem bardzo efektywny sposób. Spójrzmy na ten kod:

Zmienna MAIN_MODULE przechowuje obiekt, którego pola to elementy mojej aplikacji. To jest właśnie moduł. Jego zadanie to oddzielenie kodu programu od kontekstu globalnego. Nawet w tym prostym przykładzie widać, że bez modułu, do kontekstu globalnego przypisane zostały by aż trzy zmienne!

Aby dostać się do wnętrza modułu wystarczy użyć standardowej notacji z kropką:

Takie zastosowanie modułu można porównać do pojawiających się w innych językach, np. w Javie, przestrzeni nazw (namespace). W JS nie ma wymogu korzystania z przestrzeni nazw co powoduje, że sporo nieświadomych programistów przypisuje mnóstwo kodu do globalnego kontekstu aplikacji. A to nie jest zbyt dobrze.

Na szczęście są moduły 🙂 . Oczywiście nic nie stoi na przeszkodzie aby zagnieżdżać tego typu struktury:

Ok, ale problem globalnej przestrzeni nazw nie został całkowicie rozwiązany. Co jeśli ktoś w innej części aplikacji już stworzył zmienną o nazwie CAR? Jest to mało prawdopodobne, ale możliwe. Wtedy nadpiszemy jej wartość tą właśnie stworzoną. Cóż, i na to jest sposób:

W ten prosty sposób rozwiązujemy problem nadpisania modułu. W pierwszej linijce, program sprawdza czy w globalnej przestrzeni nazw istnieje już zmienna o nazwie CAR. Jeżeli tak, tworzona zmienna będzie referencją do niej. Jeżeli CAR nie istnieje (zwróci undefined), do naszej zmiennej przypisanie zostanie pusty obiekt. Niezależnie od wyniku tej operacji, możemy bezpiecznie dodawać pola do CAR.

Tak wyglądają moduły tworzone za pomocą literału obiektowego. Są bardzo wygodne i proste w użyciu ale wciąż mają jedną wadę. Wprawdzie zmienne nie wyciekają już do kontekstu globalnego, ale nic nie stoi na przeszkodzie aby dostać się do nich przez notacje z kropką. Innymi słowy są wciąż dostępne z każdego innego miejsca w programie. Często jest to efekt niepożądany. Programista wolałby, aby pola wykorzystywane wewnątrz modułu, były możliwe do odczytu tylko w nim, nigdzie więcej. Na szczęście i na to jest sposób.

Wzorzec modułu

Drugi sposób tworzenia modułów to wzorzec wykorzystujący JavaScript’owe samowywołujące się funkcje. Trzeba w tym miejscu pamiętać, że w JS mamy zakres funkcyjny. Oznacza to, że każda funkcja posiada własną, nieskażoną przestrzeń nazw 🙂 . Do tego, te zmienne, które pojawią się wewnątrz zakresu nie są dostępne po za nim.

Jeśli połączymy możliwości samowywołujących się funkcji, z dwoma wymienionymi wyżej cechami JSa. Otrzymamy idealny przepis na stworzenie modułu, którego pola można ‚ukryć’, tak jakby były to pola prywatne.

Oto prosty przykład:

W kodzie do zmiennej GREETER_MODULE, przypisuję to co zwraca samowywołująca się funkcja. W tym wypadku jest to obiekt zawierający metody, które odwołują się do zmiennej message. Zmienna ta ‚żyje’ tylko wewnątrz funkcji, a ponieważ jest ona wywołana tylko raz, bezpośredni dostęp do message nie będzie możliwy. Jednak dzięki domknięciom, metody zadeklarowane w funkcji (i przekazane na zewnątrz w returnie) mogą odwoływać się do message.

W ten prosty sposób powstał moduł, który ma w sobie prywatne pola, osiągalne tylko za pomocą publicznych metod. No i nic nie trafia do globalnego zakresu nazw!

Wzorzec modułu z mechanizmem zależności

Kolejnym przyjemnym aspektem tego wzorca jest to, że do tworzonego modułu można bez problemu przekazywać inne moduły. W ten sposób programista jest w stanie budować łańcuch zależności między modułami. Aby dodać istniejący moduł do wnętrza tworzonego, wystarczy przekazać wskaźnik do niego w argumencie samowywołującej się funkcji (ostatni nawias, na samym końcu przykładu).

Do mojego modułu przekażę jQuery. Wewnątrz modułu aliasem dla niego będzie znak dolara. Oto kod:

Jak widać wzorzec ten, nawet w prezentowanej tu bardzo podstawowej implementacji, ma ogromny potencjał. Trzeba jednak uważać na dwie rzeczy.

Po pierwsze, w jego wypadku nie jest tak łatwo dopilnować, czy nazwą modułu nie nadpisujemy innej globalnej zmiennej. Po drugie istnieje pewien haczyk, na który czasem łapią się programisci. Gdy publiczną metodą chcemy zwrócić prywatne pole, które zawiera obiekt, zwracana jest referencja. Po prostu trzeba o tym pamiętać i brać pod uwagę konsekwencje. Zaleca się nie stosowanie metod ustawiających/zwracających całych obiektów a tylko konkretne wartości. To zdecydowanie najbezpieczniejsze podejście 🙂

Podsumowanie

Nic nie stoi na przeszkodzie, aby łączyć te dwa wzorce. Za pomocą obiektu literałowego, można stworzyć unikatową przestrzeń nazw, a moduły wewnątrz deklarować wzorcem bazującym na domknięciach. Polecam poeksperymentować. Nic nie pomoże w zrozumieniu zagadnienia tak dobrze jak doświadczenie.

W tym miejscu zaznaczę jeszcze, że nie jest to wyczerpanie tematu modułów. Raczej jest to drogowskaz, kierujący początkowych programistów w odpowiednią stronę 🙂 Nie wspomniałem tu w ogóle o gotowych rozwiązaniach takich jak AMD czy commonJS.

W tym poście opisałem jak korzystać z modułów dostarczanych ze środowiskiem node a następnie dzięki narzędziu browserify przekształcić kod tak aby działał w przeglądarkach.

Podejść jest wiele i każdy powinien wybrać takie, które najbardziej mu odpowiada. Jednak zanim sięgniemy po gotowe rozwiązania, zachęcam do próby zgłębienia tematu na własną rękę.

Na koniec, jak zawsze, zachęcam do polubienia mojej strony na facebooku. Zawsze na bieżąco zamieszczam tam informacje o nowościach, więc warto polubić aby nie przegapić żadnego nowego wpisu.

4 przemyślenia nt. „Wzorce modułu w JavaScript.”

  1. Kiedyś coś słyszałem o modułach, ale nie chciało mi się czytać. Teraz zajrzałem i się nie zawiodłem. WOW! Zastosowałem je do swojego projektu i nawet nie wiedziałem, że mogą być takie przyjemne! Nawet chciałem sprawdzić tą „enkaspulację” danych i to jest genialne. Po mimo starań aplikacja i tak działała. Nawet po nadpisaniu paru ważnych metod. Super! 😀

  2. Fajnie wytłumaczone wzorce modułu. Dodaję do zakładek żeby w przyszłości móc kogoś odesłać do tego posta jak mnie zapyta o moduły w JS 🙂 Przy okazji, jak ktoś się zastanawia czemu przykład kodu dla wzorca modułu z mechanizmem zależności nie działa, to jest literówka – „var privateElement = $(‚div#myDiv’).” powinno się chyba kończyć średnikiem, a nie kropką 🙂 PS. To ‚div’ w selektorze też można pominąć, bo $(‚#myDiv’) zadziała zawsze tak samo (w końcu id powinny być unikatowe..).

  3. Świetny tekst, dzięki za niego. Właśnie ostatnio rozkminiałem te 2 metody tworzenia modułów w JS. Osobiście zazwyczaj wybieram wrzocec modułu bazujący na domknięciach – wygodniej wtedy operuje mi się na zmiennych i funkcjach wewnątrz modułu, natiomiat w przypadku modułu z literału obiektowego przy większej złożoności modułu (kilka funkcji, zmiennych itp) trzeba pamiętać o zmianie kontektu this wewnątrz funkcji i trzeba przekazywać go jako „that”.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *