Zrozumieć JavaScript

Rozmawiając ze znajomymi, którzy również są webdeveloperami, zauważyłem, że JavaScript jest jednym z najbardziej znienawidzonych języków. Jest kilka tego powodów, najważniejsze z nich to brak jednolitej obsługi w w przeglądarkach internetowych oraz brak klas. Pierwszy powód jest bezdyskusyjny, chociaż można zauważyć, że z upływem czasu jest coraz lepiej i minęły już te dni gdy pisało się kod osobno dla każdego browsera. Obsługa JavaScriptu jest coraz lepsza wśród przeglądarek i nawet Microsoft wziął się na poważnie do roboty z IE9. Natomiast argument o braku klas w Javascripcie uważam za trochę chybiony i wynikający z braku wiedzy o modelu obiektowym jaki został zaimplementowany w tym języku.

Javascript jest językiem obiektowo-funkcyjnym. Wszystko w Javascripcie jest obiektem łącznie z funkcjami. Fakt, że w Javascripcie nawet funkcje są obiektami czyni z niego język funkcyjny. Z elementów języka funkcyjnego posiada on również domknięcia i funkcje anonimowe co wynika bezpośrednio z tego, że funkcje są obiektami. Kilka linijek kodu by to dowieść:


//funkcja nazwana

function foo(){
    return 1;
}

console.log(foo);
//wyswietli 'foo()'

//funkcja anonimowa przypisana do stalej - stala zmienna ktorej wartosci nie mozna zmienic(immutable)
const bar = function(){
    return 1;
}

console.log(bar);
//zwraca 'function()'

const bar = 1;
//zwroci exception redeclaration of const bar

//funkcja ktora zwraca obiekt funkcji
function baz(){
    return function(){
        return 1;
    };
}

Wracając do naszych klas, należałoby się najpierw zastanowić czym jest klasa ? W językach o „tradycyjnym” modelu obiektowym (php, java, c++), klasa jest szablonem z którego tworzone są egzemplarze obiektów (instancje). Obiekty te mają ten sam sposób zachowania (współdzielą metody), różnią się jedynie wewnętrznym stanem (właściwości). Tak naprawdę są to języki zorientowane na klasy a nie na obiekty, żeby stworzyć jakikolwiek obiekt, trzeba najpierw zdeklarować klasę. W Javascripcie, dwa obiekty są tej samej klasy jeżeli mają ten sam konstruktor (lub prototyp).

//konstruktor klasy foo
function foo(){
    this.a = 'a'
    this.b = 'b'
}

var a = new foo();
var b = new foo();
console.log(a instanceof foo);
//zwroci true
console.log(b instanceof foo);
//zwroci true

Fakt ten wiążę się również z mechanizmem dziedziczenia. Jeżeli chcemy by klasa bar dziedziczyła z klasy foo to znaczy to nie mniej nie więcej tyle, że konstruktor klasy bar powinien być wywołany po konstruktorze klasy foo. Wszystko wyjaśni poniższy przykład:

//konstruktor klasy foo
            function foo(){
                this.a = 'a'
                this.b = 'b'
            }
            //konstruktor klasy bar
            function bar(){
                this.c = 'c'
                this.d = 'd'
            }
	    //konstruktor klasy baz
            function baz(){
                this.e = 'e'
                this.f = 'f'
            }
			
            //bar dziedziczy z foo
            bar.prototype = new foo();
            //baz dziedziczy z bar
            baz.prototype = new bar();
			
            x = new baz();
            console.log(x);
            console.log(x instanceof foo);
           //zwroci true

Jest to mechanizm pojedynczego dziedziczenia, jak wiadomo wielodziedziczenie jest problematyczne i w większości języków zrezygnowano z niego. Natomiast Javascript dzięki swojej funkcyjności pozwala na elastyczne komponowanie obiektów z różnych funkcji. Można np. wziąć metodę z jednego obiektu, bądź globalnego konstekstu i przypisać ją do jakiejś klasy bądź obiektu. W Javascripcie zmienna „this” zawsze odnosi się do aktualnego kontekstu. Przykład:

//funkcja foofoo
            var foofoo =  function(){
                return this.a;
            };
            //konstruktor klasy foo
            function foo(){
                this.a = 'a'
                this.b = 'b'

                this.foofoo = foofoo;
            }
            //konstruktor klasy bar
            function bar(){
                this.c = 'c'
                this.d = 'd'
                this.a = 'g';
                
                this.foofoo = foofoo;
            }
            x = new bar()
            console.log(x.foofoo());
            //zwroci 'g'
            z = new foo()
            console.log(z.foofoo());
            //zwroci 'a'

Jedną z rzeczy, którą zarzuca się JavaScriptowi jest brak enkapsulacji. Jest to oczywiście nieprawda – JS posiada enkapsulacje ale realizowaną w inny sposób. Jak już wcześniej wspomniałem między wierszami, istnieje coś takiego jak kontekst wywołania funkcji. Każda funkcja anonimowa jest też domknięciem (closure) i pamięta kontekst swojego utworzenia, więc jeżeli w konstruktorze zadeklarujemy zmienną słowem kluczowym var, to metoda obiektu ją zapamięta. W ten sposób realizuje się enkapsulacje. Przykład:

//konstruktor klasy foo
            function foo(){
                this.a = 'a'
                this.b = 'b'

                var privVar = 'a';

                this.bar = function(){
                    return privVar;
                }
            }

            z = new foo()
            console.log(z.privVar);
            //zwroci undefined
            console.log(z.bar());
            //zwroci 'a'

Ostatnią sprawą jaką chciałbym poruszyć są Singletony. W językach zorientowanych na klasy trzeba używać metod statycznych i innych trików. Natomiast w JS wystarczy zadeklarować anonimowy obiekt przypisany do globalnej zmiennej:

          Singleton = {
                a: 'a',
                b: 'b',
                c: function(){
                    return this.a;
                }
            }

            console.log(Singleton.c());

Mam nadzieje, że ten artykuł przyczyni się chociażby w jakimś stopniu do zrozumienia JS, który jest przez wielu wyklęty tak naprawdę tylko ze względu na swoją odmienność.

Wszystkie przykłady testowałem na Firefoxie 3.6.6 + Firebug. Jak zwykle czekam na feedback i wszelkie pytania ;).

Edit:

Mały update. Ponieważ, jeden z byłych czytelników tego bloga zarzucił mi, że JavaScript nie posiada klas, odsyłam wszystkich zainteresowanych tym tematem do dokumentacji na stronie Mozilli, traktującej o operatorze instanceof. Jak wiadomo, w innych językach np. PHP ów operator zwraca nazwę klasy której instancją jest dany obiekt. Dla leniwych cytuje fragment z tej tej strony:

Parameters

objectName 
    The object to test.

objectType 
    Object type, that is, the class object aka the constructor function. 

Mam nadzieje, że rozwiewa to wszelkie wątpliwości w tym temacie. Jeżeli nie, to jestem otwarty na wszelką merytoryczną dyskusję.

  1. Zielony pisze:

    „Można np. wsiąść metodę z jednego obiektu” – chyba chodziło Ci o „wziąć” :).

  2. Wojciech Soczyński pisze:

    Poprawione 😉

  3. Bardzo dobry artykuł. Przyjemnie się czyta – i co najważniejsze: jest przekonujący. Ja mimo wszystko nie mogę się zmusić do bliższej przygody z JS.

  4. Wojciech Soczyński pisze:

    Też nie powiem, że jestem jakimś szczególnym fanem JS, ale zawsze fajnie poznać coś innego dla samej inności – rozwija 😉

  5. Rodzyn pisze:

    Dobry artykuł, chociażby z tego względu, że rzadko tego typu zagadnienia JSa są poruszane w sieci.

  6. cojack pisze:

    Javascript nie ma klas.

  7. Wojciech Soczyński pisze:

    To zależy co rozumiemy pod pojęciem „klasa”, polecam dokładne przeczytanie artykułu. Jeżeli chodzi o słowo kluczowe „class” to faktycznie go nie ma, natomiast jeżeli o mechanizm programowania obiektowego to jak najbardziej są.

  8. Tuner pisze:

    Napisałeś, że prototyp to konstruktor, jeśli mnie pamięć nie myli prototyp to zupełnie co innego. Prototyp jest obiektem, zbiorem metod i atrybutów obiektów.

  9. Wojciech Soczyński pisze:

    Nie napisałem, że prototyp to konstruktor tylko konstruktor(prototyp). Miałem na myśli prototyp lub konstruktor, jeżeli chodzi o to czym jest prototyp to polecam artykuł na Wikipedii

  10. Tuner pisze:

    Rozumiem, zła interpretacja. Sam chciałem popełnić wpis o zawiłościach JS, a język ten nie jest wbrew pozorom tak banalny jak się wydaje.

  11. Wojciech Soczyński pisze:

    Mi się wydaje, że jest on po prostu inny niż większość języków z którymi mamy styczność. Dlatego też ciężko jest się do niego przyzwyczaić i może wydawać się trudnym, natomiast po głębszym przemyśleniu tego jak to wszystko działa w JS okazuje się, że jest to bardzo spójne logicznie.

  12. Kasia pisze:

    A ja tam JavaScript lubię ;P Jest niesamowicie dynamiczny i ekspresyjny, a dzięki frameworkom o większości przeglądarkowych problemów można zapomnieć. Piszę w nim naprawdę spoooro i widzę, że większość problemów wynika wyłącznie z nieznajomości języka. Ale w sumie nie ma co się dziwić, skoro większość książek/artykułów dotyczących jsa sprowadza się do DOM scripting 😛 (i tutaj wyrazy uznania dla autora za podejście :) Na koniec trochę marketingu – zainteresowanym polecałabym jedyną chyba książkę, która dotyka języka jako takiego – „JavaScript mocne strony” autorstwa Crockforda i jego wykłady na stronie Yahoo, które w zasadzie pokrywają się treścią z ksiażką http://video.yahoo.com/watch/111593/1710507 (pierwszy z serii)

  13. kilas pisze:

    oj tak, „JavaScript mocne strony” jest zdecydowanie jedną z lepszych książek o JavaScript (przynajmniej w języku polskim); choć bardzo krótka to szczegółowo opisuje zawiłości języka – zwłaszcza od strony OOP.

    co do definiowania Singletonów w JavaScript to tutaj jest to opisane szczerzej:
    http://ferrante.pl/2009/07/09/singleton-w-javascript/

    również w JavaScript idzie zaimplementować coś na wzór klasycznych klas znanych z innych języków, o czym również pisał ferrante:
    http://ferrante.pl/2010/04/02/klasyczne-klasy-w-javascript/

    btw.
    JavaScript jest nie taki zły jak go malują ;D bardzo ekspresyjny i mający wielkie możliwości.

  14. cojack pisze:

    (…) natomiast jeżeli o mechanizm programowania obiektowego to jak najbardziej są.

    Nie, nie ma. Także nie pisz o czymś o czym nie masz zielonego pojęcia, bo tylko głupoty piszesz i ludzie to czytają a później będą farmazony na forach pisać że js ma klasy. JS ma tylko obiekty, to nie są klasy.

    Teraz przynajmniej wiem jakiego bloga nie czytać więcej.

  15. Wojciech Soczyński pisze:

    @cojack: Mocnych słów użyłeś, natomiast widać, że nie zrozumiałeś ani joty z mojego artykułu, ani z mojej odpowiedzi na twój komentarz. Niestety pokazałeś się z najgorszej trollowej strony. Nie podniosłeś żadnych argumentów by udowodnić, że masz racje. To czy czytasz mojego bloga czy nie jest mi zupełnie obojętne. Wolałbym nawet, żebyś nie czytał, bo pewnie wtedy będziesz dodawał kolejne bezwartościowe komentarze.

  16. Wojciech Soczyński pisze:

    @kilas: bardzo fajne wpisy, dzięki za linki :)

  17. skworon-line pisze:

    Teraz i tak nikt (mało kto) piszę w czystym JS, żyjemy w erze frameworków, a w nich jest wszystko nawet obiektówka :)

  18. cojack pisze:

    Wyzywasz mnie od troli? Ehhh. Uparłeś się o dowód, no to masz:

    Because JavaScript is not a statically typed language, it does not provide a keyword for defining a class or object-type definition. Additionally, because JavaScript is not compiled, there would be no way to enforce the proper use of such types. However, it is still possible to define custom objects in JavaScript that behave, in many ways, like classes in C# or Java.

    http://www.xml.com/pub/a/2006/06/07/object-oriented-javascript.html

    Także powtarzam po raz ostatni, javascirpt nie ma klas. Ma konstruktory obiektów, i można to interpretować w głowie jako klasa, ale pisać o tym że to jest klasa nie możesz. Jeszcze chcesz dowodów?

  19. cojack pisze:

    A co do Singletonu, to mi na myśl przychodzi tylko jedno ja pier****. To że nie możesz utworzyć obiektu metody, to już nazywasz singletonem? Czy Ty nie potrafisz myśleć sam tylko zrzynasz z innych stron przykłady i je lekko przerabiasz. Ty wiesz w ogóle czym są konstruktory w javascript? Przykład singletonu jest zły, tego też nie możesz nazywać singletonem. Tworzenie w ten sposób obiektu nazywa się z ang Literal Notation.

    Co Ty człowieku wiesz o javascript. Nie pisz więcej o nim, dopóki się go nie nauczysz.

  20. Wojciech Soczyński pisze:

    Nie wyzywam Cię od trolli, jedynie mówię, że w swoich komentarzach na moim blogu tak się właśnie zaprezentowałeś.
    Tak dla ścisłości z tego co powiedziałeś wynika, że tylko statycznie typowane języki mogą posiadać klasy. Tym tokiem myślenia twierdzisz więc, że Php, Python i cała masa innych dynamicznych języków nie posiada klas.
    Zarzuciłeś mi poza tym, że zrzynam z innych stron, poproszę Cię więc o dowody, że tak jest, bo ja zaręczam Ci, że ten tekst jest całkowicie mojego autorstwa wraz z wszystkimi przykładami.
    Co więcej, przykład Singletona jest dobry ponieważ a)definiuje obiekt który ma jedną instancję b) definiuje obiekt który jest globalnie dostępny, a to już wystarczy by mówić, że dany obiekt jest SIngletonem. Co do sposobu zapisu w tym przykładzie jest to po prostu anonimowy obiekt w notacji JSON.
    Tak kończąc już ten temat, nie zamierzam się z Tobą kłócić bo widzę, że ani ja Cię nie przekonam do swoich racji ani ty do moich. Także jeżeli chcesz wyładować gdzieś swoje złości i frustracje, to proszę Cię abyś nie robił tego na moim blogu. Pozdrawiam i życzę miłego dnia 😉

  21. cojack pisze:

    Dobra, nie chce mi się.

  22. Rodzyn pisze:

    Cojack, generalnie polecam odrobinę melisy i przede wszystkim myślenie. Zamiast frustracji po prostu wklej kontr-przykłady… Oczywiście Javascript nie ma klas, ale taki mechanizm można umownie nazwać klasą (w zależności jak się definiuje klasę) albo po prostu imitacją klasy.
    Co do Singletona to może nie jest to klasyczny przykład tego wzorca, ale de facto spełnia swoją rolę i przypomina nieco mechanizm ze słowem kluczowym ‚object’ ze Scali.

    Pozdro i chillout. 😉

  23. cojack pisze:

    Ja czasami mam po prostu złe dni ;p

  24. Krzysiek pisze:

    Ja rowniez polecam „JS -mocne strony”. Js to jezyk skryptowy o zupelnie innnym podejsciu niz klasyczne php. Ludzie piszacy w php gdy maja cos napisac w js to od razu chca konstruktor, klasy itp. Js bazuje na prototypach… fakt, mozna jest zmusic zeby to wygladalo na dziedziczenie ale wtedy powstaja potworki. Zamiast wykorzystac atuty jezyka jak domkniecia, prototypy programisci pisza proceduralnie albo probuja tworzyc sobie cos na wzor tego co mieli w php. Czytajac ich kod az widac ze nie przestawili sie z jednego jezyka na inny.
    przyklad
    var a = new Array
    zamiast var a = []

    przyklad
    a[a.length] = ‚nowy element’
    zamiast a.push(‚nowy element’)

    do tego dochodzi czesto pisanie na zmiennych globalnych (bo tak latwiej) zamiast zapoznac sie z call, aply i arguments. Pozdrawiam

  25. Ktoś wcześniej pisał tu o klasach w JS. Owszem, można tworzyć nakładkę, która sprawi wrażenie, że programujemy w innym języku. Czy jednak o to chodzi? JS jest wolna sama w sobie. Może lepiej jest programować w duchu JS, optymalnie wykorzystując jej możliwości, niż na siłę próbować czegoś „znanego” :)

    Z mojej strony zaproponuję:
    http://www.yarpo.pl/2011/01/11/tworzenie-obiektow-w-js/

    A jeśli chodzi o klasy, to owszem, popieram ideę testowania swoich możliwości pisząc coś takiego. Nie jestem jednak zbyt wielkim zwolennikiem używania później „klas”. Z takich moich ostatnich zabaw wyszły nietypowe medium do „Ajax”:
    http://www.yarpo.pl/2011/04/08/ajax-w-oparciu-o-cookies/
    http://www.yarpo.pl/2011/03/23/ajax-w-oparciu-o-plywajaca-ramke/

  26. @Wojciech Soczyński:
    btw singletonów. Jakoś mam wątpliwości. Czym różni się singleton od zmiennej globalnej?

    Proszę nie pisać mi, definicji wzorca singleton. Także kod jest mi zbędny. Naprawdę wiem, jak napisać kod klasy implementującej ten wzorzec. Wiem, jak go używać, rozumiem po co.

    Mam jednak wątpliwości, co do potrzeby stosowania. Czy oby cokolwiek zmienia w kodzie:

    function ex() {
    global $a;
    ..
    }

    konstrukcja:

    function ex() {
    $a = singl::getInstance();
    ..
    }

    nadal kod uzależniony jest od „obcego” bytu programistycznego.

    Rozumiem zalety – nie da się nadpisać, jest jedna instancja itp, itd. Ale czym to się różni dla uniwersalności i ogólności kodu od zmiennej globalnej? :)

  27. [quote]Jest kilka tego powodów, najważniejsze z nich to brak jednolitej obsługi w w przeglądarkach internetowych oraz brak klas.[/quote]

    I problem rozwiązany:
    http://www.yarpo.pl/2011/10/11/tworzenie-klas-w-dojo/

    Dojo Toolkit pozwala w dużej mierze zapomnieć o problemach z różnymi przeglądarkami, a także wprowadza „klasy”. Samo tworzenie klasy przypomina (do wersji 1.6) podejście znane z Javy.

  28. Jest wiele frameworków, które dodają jakąś metodę tworzenie tzw. klas, ale wg mnie nie jest to w ogóle potrzebne w JS.

  29. Zgadzam się z Tobą. Jednak w Dojo jest to trochę więcej niż tylko mechanizm pozwalający tworzyć klasy dla samego faktu wprowadzenia takiego bytu.

    Świetnie pomaga to w innych mechanizmach frameworka, jak choćby tworzenie widgetów. Kto nie miał okazji pobawić się dijitami, temu polecam. Naprawdę bardzo ciekawy toolkit z tego Dojo.

    Warto poznać :)

  30. Korzystałem z dojo już jakiś czas temu w produkcyjnym projekcie i wszystko było dość spoko, dopóki używałem gotowych komponentów, schody się pojawiły, gdy chciałem trochę potweakować dataGrid, trochę się nad tym napracowałem.

  31. Akurat z tym elementem pracowałem niewiele. Póki co wspinam się po drzewach 😛

  1. There are no trackbacks for this post yet.

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.