Hej, dziś wpis trochę krótszy, z rodzaju tych “tips & tricks”… Czasem zdarza nam się natknąć na sytuację kiedy napisaliśmy w pocie czoła jakiś “zajebisty”, “zakręcony” kod JavaScript ale nie wiedzieć czemu nie działa - dzieje się tak często dlatego, że JavaScript w wielu sytuacjach zachowuje się inaczej od innych topowych języków programowania. Dziś we wpisie jedna z takich sytuacji. Domknięcie JavaScript to jedno z podstawowych zagadnień w tym języku. Czasem jednak może ono nam przysporzyć problemów…

Domknięcie JavaScript - studium przypadku

No skoro studium przypadku, to zobaczmy ten przypadek…

var module = (function () {
    var
        index = 0,
        doSmth = function () {
            index = 1;

            alert(index);
        };

    return {
        doSmth: doSmth
    }
}());

module.doSmth();

Jak widzicie przykład bardzo prosty - moduł, zawierający zmienną prywatną index, oraz funkcję doSmth. Funkcja ta wykorzystuje domknięcie JavaScript (ang. closure), a więc ma swobodny dostęp do zmiennej index, dlatego może dokonać jej modyfikacji i wyświetlić jej zawartość za pomocą polecenia alert.

Problemy…

Ok, to teraz do kodu siada inny programista i stwierdza, że potrzebuje mieć dostęp do zmiennej index na zewnątrz modułu:

var module = (function () {
    var
        index = 0,
        doSmth = function () {
            index = 1;

            alert(index);
        };

    return {
        index: index,
        doSmth: doSmth
    }
}());

module.doSmth();
alert(module.index);

No świetnie, tylko że to nie zadziała tak jak on się tego spodziewa… Powyższy kod wyświetli najpierw wartość 1, a następnie wartość 0 (tutaj jsfiddle jeśli ktoś chce sobie kliknąć) Pytanie tylko dlaczego 0 a nie 1 skoro wcześniej wywołano funkcję, która teoretycznie powinna zmienić już wartość zmiennej index.

Rozwiązanie

No to spójrzmy co się tutaj dzieje:

  1. W momencie tworzenia zmiennej module wywoływana jest funkcja natychmiastowa, która zwraca obiekt zawierający dwie właściwości: index oraz doSmth. Do właściwości tych odpowiednio kopiowana jest wartość początkowa zmiennej prywatnej index oraz kopiowana jest referencja do funkcji doSmth (to nie są tak na prawdę ta sama zmienna i funkcja)
  2. Wywołanie module.doSmth() powoduje zmianę lokalnej zmiennej index - właściwość index zwróconego obiektu (tego który siedzi teraz w zmiennej module) pozostaje nie naruszona
  3. Z kolei wywołanie module.index zwraca wartość właściwości zwróconego na początku obiektu - ta wartość nie została zmieniona przez wywołanie module.doSmth()

Ok, wszystko jasne… Rozwiązaniem mogłoby być użycie this w funkcji doSmth:

var module = (function () {
    var
        index = 0,
        doSmth = function () {
            this.index = 1;

            alert(this.index);
        };

    return {
        index: index,
        doSmth: doSmth
    }
}());

module.doSmth();
alert(module.index);

No teraz działa, w obu przypadka wyświetla się wartość 1. Tylko, że to nie do końca o to chodzi ponieważ teraz nie dotykamy w ogóle zmiennej lokalnej index funkcji natychmiastowej, można by ją całkiem wywalić i inicjować ją jakąś wartością podczas tworzenia obiektu zwracanego przez tę funkcję. Dlatego właśnie, nie powinno się tego robić w ten sposób - zamiast upubliczniać zmienną prywatną, tak jak to zrobił nasz przykładowy, wyimaginowany programista należałoby raczej zastosować funkcję pobierającą oraz funkcję ustawiającą (getter i setter)… I tym akcentem zakończę swój wywód ;)