Wzorce projektowe Javascript: moduł (Module Pattern), IIFE

Artykuł dodany: 17 listopada 2015.

Stopień trudności (1 - dla początkujących, 5 - dla ekspertów): 1

Nie ma co ukrywać. Wzorzec modułu, mimo swoich lat (początki sięgają 2003 roku), jest jednym z najpopularniejszych rozwiązań w JavaScript. Bardzo mocno spopularyzowany został dzięki, nieistniejącej już, bibliotece YUI. W ECMAScript 6, pomimo wprowadzenia pełnoprawnych klas, dalej pewne problemy rodzi zasięg zmiennych i ich widoczność – nie ma standardowego w innych językach modyfikatora “private”. Istnieje co prawda WeakMap który może nam posłużyć do emulacji prywatności, jednak stosowanie tego obiektu nie jest do końca naturalne (dla programistów piszących na co dzień w językach takich jak Java, C#, PHP).

We wszystkich przykładach do wyświetlania danych używam konsoli. Możesz użyć wbudowaną w przeglądarkę, lub zainstalować Firebug. Konsolę uruchamiasz w większości przeglądarek skrótem klawiszowym ctrl+shift+i lub F12.

Wracając jednak do naszego wzorca modułu, na czym jego koncepcja polega? By lepiej to zrozumieć przeanalizujmy podstawy samego języka. Tak jak zmienne definiujemy poprzez słowo kluczowe var (lub od ES6 także const lub let):

var zmienna = 'przykładowy string';
// od ECMAScript 6 możemy zdefiniować też stałą, która raz zdefiniowana nie może być zmieniona
// przyjmuje się że stałe zapisujemy dla rozróżnienia wielkimi literami
const STALA = 'niezmienny string';

// odwołanie się do zmiennej wyświetli przykładowy string
console.log(zmienna); // przykładowy string

tak definicja funkcji wygląda następująco:

function name([parametr[, parametr[, ... parametr]]]) {
   // wyrażenia
}

Funkcja musi posiadać określoną przez nas nazwę oraz dowolną, opcjonalną, liczbę parametrów. Nasza przykładowa funkcja może zatem mieć postać:

function test() {
    console.log('Nasza pierwsza funkcja `test`');
}

Powyżej znajduje się jednak wyłącznie sama deklaracja funkcji. Aby w konsoli pojawił się napis musimy funkcję wywołać:

test();

Lub jeśli chcemy przekazać parametr:

function test(imie) {
    console.log('Imię przekazane z zewnątrz: ' + imie);
}

test('Adam');

Co się stanie gdybyśmy chcieli wyświetlić zmienną var zmienna?

var zmienna = 'przykładowy string';

function test() {
    var lokalna = 'zmienna lokalna';
    console.log(zmienna, ' oraz ', lokalna);
}

test();

W przykładzie mamy do czynienia z bardzo ważną sprawą jaką jest zasięg zmiennych. Jeżeli zmienne albo funkcje nie są zawarte w innej funkcji, będą miały zasięg globalny. Czyli można je używać gdzie tylko chcemy wewnątrz całej aplikacji. Dlatego bez problemu wyświetli nam się ‘przykładowy string’. Z kolei zmienna `lokalna` została zdefiniowana wewnątrz naszej testowej funkcji, dlatego będzie widoczna tylko i wyłącznie w niej samej. Na nic nie zda się odwołanie:

test();
console.log(lokalna); // ReferenceError: lokalna is not defined

Jest to bardzo ważna dla nas informacja w kontekście całego wzorca modułu, który ukrywa przed dostępem z zewnątrz pewne dane, czyni je prywatnymi.

Patrząc na konsolę być może zauważyłeś czytelniku, iż w zakładce “DOM” wszystkie wartości znajdują się w obiekcie “window”. Jest to specjalna wartość która reprezentuje aktualne okno przeglądarki. W przypadku otwartych kilku kart każda z nich ma swój własny obiekt window. Nasza funkcja test() oraz zmienna znajdują się bezpośrednio w nim i dlatego w każdym miejscu mamy do nich również dostęp poprzez odwołania:

window.test();
console.log(window.zmienna);

Wykorzystując ten fakt możemy łatwo uczynić zmienną `lokalna` globalną:

function test() {
    var lokalna = window.lokalna = 'zmienna lokalna';
}

test();
console.log(lokalna);

Do tej pory zdefiniowaliśmy funkcję, jednak posiadała ona swoją nazwę – test. Po angielsku na definicję taką mówimy “function declaration”. Jednak istnieje jeszcze inna składnia – “function expression”:

function [name]([parametr[, parametr[, ... parametr]]]) {
   // wyrażenia
}

Tym razem opcjonalna jest też nazwa funkcji dlatego mówimy na nią “funkcja anonimowa”. Stosując poprzedni kod możemy zdefiniować:

function () {
    var lokalna = 'zmienna lokalna';
}

Pojawi się jednak mały problem. Po pierwsze, sama definicja zwróci błąd: “function statement requires a name”. Po drugie, jak taką funkcję wywołać? Istnieją dwa sposoby. Możemy przypisać naszą anonimową funkcję do zmiennej i ją wywołać:

var test = function () {
    console.log('Nasza pierwsza funkcja anonimowa');
}

test();

Lub też jeszcze lepiej:

var test = function () {
    console.log('Nasza pierwsza funkcja anonimowa');
}();

Ale jest też drugi sposób który nosi nazwę IIFEImmediately-invoked function expression.

(function () {
    console.log('Nasza pierwsza funkcja IIFE');
}());

Dzięki takiemu zapisowi funkcja jest natychmiast wykonana zaraz po definicji oraz funkcja wewnątrz generuje swój własny zasięg. Uzbrojeni w taką wiedzę możemy teraz spokojnie stworzyć swój pierwszy moduł.

Module pattern

Warto zaznaczyć że powstało co najmniej kilka różnych wariacji tego wzorca, stąd też definicja czy wykonywanie mogą odrobinę się różnić. W podstawowym zakresie definicja wzorca modułu może wyglądać tak:

var modulTestowy = (function () {

    // własność prywatna
    var lokalna = 'zmienna lokalna';

    // metoda prywatna
    var metodaPrywatna = function () {
        return 'Metoda prywatna';
    }

    // Wartości zwracane stają się publiczne
    return {
        zmienna : 'zmienna publiczna',
        init : function () {
            console.log('Moduł zainicjowany.', metodaPrywatna(), ' wywołana');
        }
    };

})();

// Metody i zmienne publiczne
modulTestowy.init(); // Moduł zainicjowany. Metoda prywatna wywołana
console.log(modulTestowy.zmienna); // zmienna publiczna

// Metody i zmienne prywatne
console.log(modulTestowy.lokalna); // undefined
modulTestowy.metodaPrywatna(); // TypeError: modulTestowy.metodaPrywatna is not a function

Uzyskaliśmy podział na metody i zmienne prywatne – widoczne tylko wewnątrz modułu, natomiast cała reszta, zwracana za pomocą słowa kluczowego return, staje się widoczna na zewnątrz modułu – publiczna.

Istnieje też inna technika umożliwiająca ekspozycję metod prywatnych na zewnątrz.

var modulTestowy = (function () {

    // własność prywatna
    var lokalna = 'zmienna lokalna';

    // metoda prywatna
    var test = function () {
        return 'metoda prywatna';
    };

    // Wartości zwracane stają się publiczne
    return {
        lokalna : lokalna,
        test : test
    };

})();

console.log(modulTestowy.lokalna); // zmienna lokalna
console.log(modulTestowy.test()); // metoda prywatna

Bardzo często spotykane w sieci (np. poprzez wtyczki jQuery) jest rozszerzenie (augmentacja) obecnego kodu o kod innego modułu. Może to wyglądać następująco:

var modulDrugi = (function () {

    return {
        sumowanie: function (a, b) {
            return a + b;
        }
    };

})();

var modulTestowy = (function (mod2) {

    var sumowanie = function (a, b, c) {
        console.log(mod2.sumowanie(a, b) + c);
    }

    return {
        sumowanie: sumowanie
    }

})(modulDrugi);

modulTestowy.sumowanie(1, 2, 3); // 6

Do modułu testowego przekazujemy `modulDrugi` który lokalnie widoczny jest jako zmienna `mod2`. Modułów takich możemy przekazać dowolną ilość.

Jest jeszcze jeden sposób tworzenia aplikacji modułowej. Najlepiej koncepcję powinien wyjaśnić kod:

var wwwgo = wwwgo || {};

wwwgo.matematyka = function () {

   return {
        sumowanie: function (a, b) {
            return a + b;
        }
    };

}();

wwwgo.tekst = function () {

   return {
        dlugosc: function (a) {
            return a.length;
        }
    };

}();

wwwgo.config = {
    pi: Math.PI
};

console.log(wwwgo.matematyka.sumowanie(2, 4)); // 6
console.log(wwwgo); // zbiór obiektów

`wwwgo` jest w tym wypadku kontenerem dla całej naszej aplikacji. Może przechowywać moduły, stałe, funkcje.

Podsumowanie

Mimo upływającego czasu wzorzec modułu jest w dalszym ciągu bardzo popularny. To jego stosowanie zapoczątkowało modułową budowę aplikacji, wprowadziło logiczny podział na publiczne i prywatne zmienne. Chociaż obecnie częściej stosowane są moduły AMD, UMD, CommonJS warto wiedzieć na czym cała idea polega. Mogę się założyć, że nieraz jeszcze spotkasz Czytelniku kod, oparty w pełni na tym właśnie rozwiązaniu.

Komentarze

Nie ma jeszcze żadnych komentarzy do wyświetlenia. Może chcesz zostać pierwszą osobą która podzieli się swoją opinią?

Dodaj komentarz

*
Nazwa zostanie wyświetlona wraz z komentarzem. Możesz też utworzyć nowe konto w serwisie, dzięki czemu uzyskasz dodatkową funkcjonalność.
*
Akceptowana jest ograniczona składnia Textile. Wszystkie tagi HTML zostaną usunięte.