JavaScript nie stosuje rozwiązań znanych z popularnych języków umożliwiających programowanie obiektowe jak C++, Java czy PHP. Niewiele osób wie, że dzięki sprytnemu wykorzystaniu funkcji i zmiennych stworzymy implementację klasy, zawierającą konstruktor, składowe i metody, które mogą być publiczne, prywatne bądź też statyczne.
Jest to możliwe, ponieważ funkcje w języku JavaScript mają status obiektów pierwszej klasy, co oznacza, że funkcja traktowana jest równorzędnie z innymi typami danych (dzięki czemu może być zapisana w zmiennej lokalnej, przekazywana jako argument do innej funkcji czy zwracana jako jej wynik).
Oto przykład przypisania zdefiniowanej funkcji do zmiennej, a następnie jej wywołanie:
Wiedząc już, że funkcja będzie pełniła rolę obiektu zacznijmy od stworzenia konstruktora.
Konstruktor
Konstruktor to funkcja zdefiniowana wewnątrz klasy, która inicjuje obiekt w chwili jego tworzenia. Wywoływana jest ona automatycznie w chwili inicjacji obiektu za pomocą operatora new.
W klasycznych językach programowania konstruktor jest specjalną publiczną metodą wewnątrz klasy. Nie zwraca ona żadnego wyniku, oraz jest uruchamiana w momencie tworzenia obiektu.
W języku JavaScript rolę konstruktora pełni kod funkcji będącej odpowiednikiem klasy. Argumenty takiej funkcji są zatem parametrami konstruktora.
Zobaczmy przykład:
Publiczne i prywatne składowe
W przykładzie wyżej klasa miała pola publiczne. Oznacza to, że każdy mógł odczytać bądź zmienić dane, odwołując się do nich np. w następujący sposób:
Metody
Utwórzmy klasę Tablica, z publicznymi składowymi oraz z publiczną metodą zwracającą ilość komórek:
Istnieje jeszcze jeden sposób na utworzenie publicznej metody. Ten sam efekt uzyskamy używając "prototypów".
Czym są prototypy?
Są to narzędzia JavaScriptu pozwalające przyłączać lub przypisywać metody do "planu" funkcji. Metody dodane do klasy (funkcji) prototypowej nie powielają się w czasie tworzenia obiektów tej klasy. Metody i właściwości dodane do klasy za pośrednictwem prototypu są automatycznie udostępniane wszystkim jej instancjom.
Zajmijmy się teraz prywatnymi metodami. W przypadku zmiennych prywatnych użycie referencji do metody nie będzie możliwe, ponieważ działają one tylko w obrębie funkcji, w której zostały zadeklarowane. W takim przypadku mamy do wyboru dwie opcje. Pierwsza to funkcja inline zdefiniowana w konstruktorze, a druga to funkcja przypisana do zmiennej z deklaracją var. Obie działają tylko w zakresie swojej klasy.
W przypadku klasy z polami publicznymi, aby prywatne metody poprawnie działały musimy przypisać wartości do tymczasowych zmiennych.
Zmienne statyczne
Zmienne statyczne w danym bloku programu posiadają dokładnie jedną instancję, niezależną od ilości utworzonych obiektów. Definiuje się je jako właściwości funkcji.
// Przypisanie funkcji DisplayGreeting(hour) // do zmiennej display var display = function DisplayGreeting(hour) { if (hour >= 22 || hour <= 5) document.write("Czas spac!"); else document.write("Pracuj dalej!") } // Wywolanie funkcji DisplayGreeting() // poprzez zmienna display display(10);Jeśli decydujemy się przechowywać fragment kodu w zmiennej, warto rozważyć stworzenie "funkcji anonimowej", czyli bez nazwy. Tworzy się ją poprzez usunięcie nazwy z definicji.
var display = function (hour) { ... } display(10);Funkcje anonimowe wykorzystuje się do przekazywania do ciała funkcji, w postaci argumentu, fragmentów kodu niewykorzystywanego w innym miejscu programu.
Wiedząc już, że funkcja będzie pełniła rolę obiektu zacznijmy od stworzenia konstruktora.
Konstruktor
Konstruktor to funkcja zdefiniowana wewnątrz klasy, która inicjuje obiekt w chwili jego tworzenia. Wywoływana jest ona automatycznie w chwili inicjacji obiektu za pomocą operatora new.
var myHello = new showHelloWorld();Próba powołania instancji klasy (czyli tutaj obiektu myHello) skutkuje wywołaniem kodu zapisanego w funkcji, zupełnie tak, jakby nastąpiło jej bezpośrednie wywołanie.
W klasycznych językach programowania konstruktor jest specjalną publiczną metodą wewnątrz klasy. Nie zwraca ona żadnego wyniku, oraz jest uruchamiana w momencie tworzenia obiektu.
W języku JavaScript rolę konstruktora pełni kod funkcji będącej odpowiednikiem klasy. Argumenty takiej funkcji są zatem parametrami konstruktora.
Zobaczmy przykład:
function helloWorld(hour) { // Konstruktor klasy inicjuje pole hour this.hour = (hour) ? hour : (new Date()).getHours(); // Definicja funkcji wyswietlajacej pozdrowienie this.DisplayGreetings = function() { if (this.hour >= 22 || this.hour <= 5) document.write("Czas spac!"); else document.write("Pracuj dalej!") } } var myHello = new helloWorld(); myHello.DisplayGreetings();Tworzony jest obiekt klasy helloWorld. Klasa to posiada jedną publiczną składową - hour, która jeśli została podana, przyjmuje zadaną wartość, a jeśli nie - sprawdza aktualną godzinę w systemie. Obiekt ten posiada także jedną publiczną metodę (funkcję anonimową) o nazwie DisplayGreetings(), której zadaniem jest wyświetlenie komunikatu w zależności od godziny zapisanej w składowej.
Publiczne i prywatne składowe
W przykładzie wyżej klasa miała pola publiczne. Oznacza to, że każdy mógł odczytać bądź zmienić dane, odwołując się do nich np. w następujący sposób:
alert(myHello.hour); // wyswietla aktualną godzinęSłowo this to odwołuje się do obiektu, na którym konstruktor jest wywoływany. Sposób ten pozwala na tworzenie publicznych zmiennych.
function Tablica(wiersze, kolumny) { // Składowe publiczne this.wiersze = wiersze; this.kolumny = kolumny; } var tab1 = new Tablica(2,4); // wyświetla "2 4" document.write(tab1.wiersze + " " + tab1.kolumny);Aby stworzyć prywatnego uczestnika klasy, będziemy musieli zadeklarować zmienną wewnątrz funkcji. Odbywa się to dzięki poprzedzeniu nazwy zmiennej deklaracją var (co znaczy tyle, że zmienna ma zasięg lokalny). Stworzone w ten sposób składowe są niewidoczne z poziomu instancji funkcji, w naszym przypadku możemy powiedzieć, że są to prywatne zmienne.
function Tablica(wiersze, kolumny) { // Składowe prywatne var wiersze = wiersze; var kolumny = kolumny; } var tab2 = new Tablica(5,2); // wyświetla "undefined undefined" document.write(tab2.wiersze + " " + tab2.kolumny);Zmienne wiersze i kolumny obiektu tab2 nie są dostępne spoza ciała funkcji. Dzieje się tak, ponieważ składowe osiągane za pomocą słowa kluczowego this oraz deklarowane poprzez var przechowywane są w różnych miejscach pamięci. Wyjątkiem jest kontekst globalny, w którym nie wprowadza się podziału na zmienne (czyli składowe prywatne) i właściwości (składowe publiczne) obiektu.
Metody
Utwórzmy klasę Tablica, z publicznymi składowymi oraz z publiczną metodą zwracającą ilość komórek:
function Tablica(wiersze, kolumny) { // Składowe publiczne this.wiersze = wiersze; this.kolumny = kolumny; // Metody publiczne this.getLiczbaKomorek = function() { return this.wiersze * this.kolumny; } } var tab3 = new Tablica(1,5); // wyświetla 5 document.write(tab3.getLiczbaKomorek());Sposób jest intuicyjny i działa poprawnie, ale nie jest najlepszym rozwiązaniem. Kod zapisany w takiej formie, w chwili zainicjowania obiektu, stworzy nowe zmienne zawierające liczbę wierszy oraz kolumn, ale stworzy także nową kopię metody getLiczbaKomorek(), której nie potrzebujemy. Problem ten nazywany jest "niewystarczającym modelem obiektowym JavaScript". Wyobraźmy sobie, utworzenie kilku tysięcy obiektów klasy Tablica. Każda taka instancja zawierała będzie kod metody w niej zadeklarowanej. Takie praktyki nie najlepiej świadczą o roztropności programisty, ponieważ nie dba on o pamięć zużywaną przez jego aplikację. Stan obiektu musi być inny dla każdego z nich, ale metody klas mogą być wspólne. Jak tego dokonać? Wystarczy odwołać się do funkcji zewnętrznych...
function Tablica(wiersze, kolumny) { // Składowe publiczne this.wiersze = wiersze; this.kolumny = kolumny; // Metody publiczne this.getLiczbaKomorek = getLiczbaKomorek; } function getLiczbaKomorek() { return this.wiersze * this.kolumny; } var tab4 = new Tablica(3,15); // wyświetla 45 document.write(tab4.getLiczbaKomorek());To co zrobiliśmy to utworzenie referencji do zewnętrznej funkcji. Teraz wszystkie obiekty klasy Tablica będą korzystać z tej samej funkcji. Takie rozwiązanie jest znacznym ulepszeniem, ponieważ oszczędza moc obliczeniową i pamięć komputera.
Istnieje jeszcze jeden sposób na utworzenie publicznej metody. Ten sam efekt uzyskamy używając "prototypów".
Czym są prototypy?
Są to narzędzia JavaScriptu pozwalające przyłączać lub przypisywać metody do "planu" funkcji. Metody dodane do klasy (funkcji) prototypowej nie powielają się w czasie tworzenia obiektów tej klasy. Metody i właściwości dodane do klasy za pośrednictwem prototypu są automatycznie udostępniane wszystkim jej instancjom.
- Każda funkcja w języku JavaScript ma właściwość prototype, która jest jednocześnie niezależnym obiektem.
- Aby dodać uczestników do funkcji prototypowej należy dodać ich do właściwości prototype tej funkcji.
- Każdy obiekt typu prototype dysponuje właściwością constructor, która przechowuje wskaźnik do funkcji konstruktora.
- Funkcje i zmienne tworzące konstruktor nie są dostępne z poziomu funkcji dodanych do jego prototypu.
- Dodanie nowego uczestnika do prototypu sprawia, że staje się on natychmiast dostępny dla wszystkich obiektów (nawet dla tych, które już istnieją)
- Funkcja prototypu może zyskać nowych uczestników dopiero po zdefiniowaniu jej ciała
function Tablica(wiersze, kolumny) { // Składowe publiczne this.wiersze = wiersze; this.kolumny = kolumny; } Tablica.prototype.getLiczbaKomorek = function () { return this.wiersze * this.kolumny; } var tab5 = new Tablica(31,15); document.write(tab5.getLiczbaKomorek());Są to dwa sposoby na tworzenie publicznych metod, które to mogą być wykonywane z dowolnego miejsca w kodzie.
Zajmijmy się teraz prywatnymi metodami. W przypadku zmiennych prywatnych użycie referencji do metody nie będzie możliwe, ponieważ działają one tylko w obrębie funkcji, w której zostały zadeklarowane. W takim przypadku mamy do wyboru dwie opcje. Pierwsza to funkcja inline zdefiniowana w konstruktorze, a druga to funkcja przypisana do zmiennej z deklaracją var. Obie działają tylko w zakresie swojej klasy.
function Tablica(wiersze, kolumny) { // Składowe prywatne var _wiersze = wiersze; var _kolumny = kolumny; // Prywatne metody // OPCJA 1 function _zwrocKomorki() { return _wiersze * _kolumny; } // OPCJA 2 var _zwrocKomorki1 = function() { return _wiersze * _kolumny; } // obie funkcje zwraca 465 document.write(_zwrocKomorki()); document.write(_zwrocKomorki1()); } var tab5 = new Tablica(31,15); // blad: tab5._zwrocKomorki is not a function document.write(tab5._zwrocKomorki());Próba odwołania się do funkcji z zewnątrz klasy, zwraca do konsoli błąd tab5._zwrockKomorki is not a function.
W przypadku klasy z polami publicznymi, aby prywatne metody poprawnie działały musimy przypisać wartości do tymczasowych zmiennych.
function Tablica(wiersze, kolumny) { // Składowe publiczne this.wiersze = wiersze; this.kolumny = kolumny; // Prywatne metody // Stwórz tymczasowe zmienne var zm1 = this.wiersze; var zm2 = this.kolumny; // OPCJA 1 function _zwrocKomorki() { return zm1 * zm2; } // OPCJA 2 var _zwrocKomorki1 = function() { return zm1 * zm2; } // obie funkcje zwraca 465 document.write(_zwrocKomorki()); document.write(_zwrocKomorki1()); } var tab5 = new Tablica(31,15); // zwraca undefined document.write(tab5.zm1); // zwraca 31 document.write(tab5.wiersze);
Zmienne statyczne
Zmienne statyczne w danym bloku programu posiadają dokładnie jedną instancję, niezależną od ilości utworzonych obiektów. Definiuje się je jako właściwości funkcji.
function Czlowiek(imie,kraj) { Czlowiek.populacja++; // lub // this.constructor.populacja++; this.imie = imie; this.kraj = kraj; } Czlowiek.populacja = 0; czlowiek1 = new Czlowiek("Milena","Polska"); czlowiek2 = new Czlowiek("Kate","USA"); czlowiek3 = new Czlowiek("Sofia","Francja"); // wyswietla 3 document.write(Czlowiek.populacja);W konstruktorze zadeklarowano instrukcję, która inkrementuje właściwość funkcji Człowiek o 1, przy każdym wywołaniu. Poza tą "klasą", zmiennej statycznej populacja przypisano początkową wartość równą zero. Po utworzeniu trzech obiektów, jej wartość automatycznie się zwiększa.
Podsumowanie
- Zmienne publiczne
Deklarowane: this.nazwaZmiennej
Mogą być czytane i zmieniane poza klasą. - Zmienne prywatne
Deklarowane: var nazwaZmiennej
Dostęp do niej możliwy tylko wewnątrz danej funkcji. - Metody publiczne
Deklarowane: nazwaKlasy.prototype.nazwaMetody = function() { .... }
Deklarowane: this.nazwaMetody = nazwaMetody; (poza funkcją musi znajdować się kod funkcji nazwaMetody())
Dostęp poza klasą i w klasie. - Metody prywatne
Deklarowanie: funkcja inline w obrębie konstruktora,
Deklarowanie: var nazwaMetody = function() { .. }
Dostęp tylko w zakresie kodu funkcji (klasy). - Zmienne statyczne
Deklaracja poza klasą: nazwaKlasy.nazwaZmiennej = .... ;
Zmiany wewnątrz klasy (np w konstruktorze).
Brak komentarzy:
Prześlij komentarz