W poprzedniej części

W pierwszej części artykułu opisałem w jaki sposób sterować kontekstem wywołania funkcji. Teraz chciałbym opisać, co zrobić z zagnieżdżonymi domknięciami oraz… co zrobić, aby nie używać operatorów `this’ i `new’.

Zagnieżdżone domknięcia

Czy zatem nietypowe sytuacje opisane wcześniej – to nie jedyne problemy, na jakie możemy się natknąć? Lub też – na pewno się natkniemy? Nie :D

Jak widać JavaScript – niczym święty Mikołaj – z każdą chwilą ma dla nas więcej niespodzianek! Na pewno część z Was wie, że można zdefiniować funkcje wewnątrz funkcji (przykładem tego była metoda `Constructor.test’). W tamtym wypadku kontekst był poprawnie wiązany. Co jednak, gdy zrobimy coś takiego:

var x = 'window';
function Constructor3
{
    this.x = 'Constructor3';
    this.test = function()
    {
        var innerClosure = function()
        {
            console.log(this.x); // ‘window’ ?!
        };
        innerClosure();
    };
};

Czy domyślasz się, dlaczego zostało wyświetlone “window” zamiast oczekiwanego “Constructor3”? Według większości programistów JavaScript jest to zwyczajnie błąd języka. Niestety – obowiązuje. można oczywiście `innerClosure’ stworzyć w ten sposób:

this.innerClosure = ...

Jednak wtedy będzie ona publiczna. A czasem chcemy ukryć część logiki przed światem (tak, w JavaScript istnieją zmienne prywatne w obiektach, źródło: http://www.yarpo.pl/2011/01/11/wzorzec-modulu-dobre-praktyki/#private). Co zatem zrobić, aby zachować poufność tej funkcji, ale jednocześnie zmusić ją do prawidłowego działania?

rozwiązanie 1:

function Constructor3
{
    this.x = 'Constructor3';
    this.test = function()
    {
        var innerClosure = function()
        {
            console.log(this.x);  // ‘Constructor3’
        };
        innerClosure.apply(this); // równie dobrze można użyc `call’
    };
};

rozwiązanie 2 (IMO lepsze):

function Constructor3
{
    var that = this; // #
    this.x = 'Constructor3';
    this.test = function()
    {
        var innerClosure = function()
        {
            console.log(that.x); // # ‘Constructor3’
        };
        innerClosure();
    };
};

Zwróć szczególną uwagę na linie oznaczone #.

Nie możemy nadpisać wartości operatora `this’. Możemy jednak przypisać referencję do niego do zwykłej zmiennej, która dzięki właściwościom domknięć zachowa swoją wartość w zagnieżdżonych domknięciach (chyba, że ktoś tam przypisze jej inną wartość, sama jednak nie zmieni wartości w zagnieżdżonej funkcji – w przeciwieństwie do `this’). Jako zmienne, do których przypisujemy właściwy kontekst najczęściej występują: thatself_this. Choć oczywiście jest to tylko nazwa zmiennej i jak każda inna zmienna może się nazywać jak tylko programista zechce.

rozwiązanie 3 (IMO – jeśli możliwe – najlepsze):

Nie używać `this’.

Jak unikać `this’?

Po co zatem tyle pisać, skoro można pisać programy bez wykorzystania operatora `this’? JavaScript ma dwa problemy:

  1. w nazwie ma “Java” – choć jedynie bardzo powierzchownie przypomina Javę,
  2. w nazwie ma “script” – choć już nie jest małym dodatkiem do prawdziwego języka programowania. To bardzo dynamicznie rozwijany język, który jeszcze nie pokazał wszystkiego, co potrafi.

źródło: http://www.crockford.com/javascript/javascript.html

O tym, dlaczego używanie operatora `new’ (który najczęściej wywołuje konieczność stosowania `this’) nie jest najwłaściwszym rozwiązaniem można przeczytać w słynnym artykule “We hardly new ya”.

Aby unikać operatora `this’ starczy używać wzorca fabryki obiektów:

lub też korzystać z nowości dodanych do najnowszej wersji JavaScript (właściwie ECMAScript, który wraz z DOM i BOM tworza JavaScript):

Choć wśród wielu zalet (np. prywatne zmienne, brak problemów z kontekstem, wszystkie zalety programowania opartego o domknięcia) ma on swoje wady – choćby duplikacja w każdym obiekcie wytworzonym przez fabrykę definicji funkcji.

Można także zastosować jedno z nowszych podejść – AMD (ang. Asynchronous Module Definition), gdzie, co prawda można, ale nie trzeba, używać operatorów `this’. Można korzystać z fabryki obiektów, a dodatkowo uzyskać za darmo rozwiązanie wielu problemów z zależnościami między modułami oraz czasem ładowania skryptów:

KONWENCJA NAZW

Być może zwróciłeś  uwagę, że czasem używałem małych, a czasem wielkich liter do nazywania konstruktorów. Jest to prosta konwencja:

  • Jeśli funkcja wymaga użycia operatora `new’ – jej nazwa zaczyna się WIELKĄ literą
  • Jeśli funkcja nie wymaga użycia operatora `new’ – małą literą

Jest to bardzo skuteczny sposób na unikanie wielu błędów zaproponowany przez javoscriptowego guru – Douga Crockforda (twórca m. in. formatu JSON, biblioteki YUI, narzędzi: JSLint, JSMin, autor kilku linkowanych w tym wpisie artykułów). Można także korzystać z trybu ścisłego (co jednak nie zawsze w istniejących od wielu lat projektach jest realne).

Podsumowanie

Jak widać JavaScript pozwala na bardzo wiele. Nie oznacza to, że należy korzystać ze wszystkich mechanizmów, które dostarcza. Sadzę, że nie najlepszym pomysłem jest mieszanie w jednym projekcie obu podejść. Warto na samym początku podjąć decyzję, które podejście nam bardziej odpowiada i w przypadku wybrania opcji “javowej” (z `this’ i `new’) upewnić się, że wszyscy programiści w projekcie znają zasady, na jakich działa JavaScript.

Jeśli po przeczytaniu tego artykułu stwierdzasz, że JavaScript jest jeszcze gorszym językiem niż myślałeś, to pamiętaj, że choćby u podstaw jQuery (chyba aktualnie najbardziej lubianej biblioteki) stoi właśnie możliwość sterowania kontekstem wywołania. To nie jest wada języka. To jest feature. Może dziwny na początku, ale posiadający potężny potencjał.

WARTO PRZECZYTAĆ

Nadal masz siły na poznawanie tajników JavaScript? Proponuję dowiedzieć się więcej o domknięciach i hoistingu w JavaScript:

A dla osób ciągle głodnych wiedzy polecam 2 świetne książki:

Dziękuję za uwagę. Życzę przyjemnej zabawy z kodem.