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.
Oto przykład przypisania zdefiniowanej funkcji do zmiennej, a następnie jej wywołanie:
// 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
Utwórzmy analogiczną klasę wykorzystując właściwość
prototype.
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.