Witam ponownie i zapraszam na kolejny odcinek cyklu “przygotowania do MCSD 70-480”. Zgodnie z tytułem, w dzisiejszym wpisie zajmiemy się kwestią obiektów i metod w JavaScript oraz ogólnie tematem programowania obiektowego w tym języku. Tym oto sposobem zamkniemy omawianie pierwszego z czterech głównych tematów jakie należy opanować przed egzaminem - mowa o “Implement and Manipulate Document Structures and Objects” stanowiącym 24% wszystkich pytań podczas testu. Jak zwykle nie ma co przedłużać, sprawdźmy jak przedstawiają się obiekty i metody w języku JavaScript!

Obiekty natywne

Język JavaScript dostarcza nam do kilka natywnych/wbudowanych klas, z których możemy korzystać podczas tworzenia skryptów. Poniżej lista najważniejszych z nich (wraz z linkami do w3schools.com zawierającymi ich dokładniejszy opis):

  • Number - reprezentuje liczbę
  • Boolean - pozwala na konwersję wartości “nie-boolowskich” na boolowskie
  • String - umożliwia przechowywanie tekstu i manipulowanie nim
  • Array - służy do przechowywania tablic, czyli zmiennych zawierających wiele wartości
  • Date - używane jest do pracy z datami
  • Math - dostarcza funkcji matematycznych realizujących różnego rodzaju obliczenia
  • RegExp - umożliwia tworzenie wyrażeń regularnych

Obiekty takie, są często tworzone w domyśle, tzn. jeśli przypisujemy do jakiejś zmiennej tekst, wówczas tak na prawdę, do zmiennej przypisywany jest obiekt ‘String’, jeśli liczbę to przypisywany jest obiekt ‘Number’ itd. Ponadto każdy z tych obiektów posiada szereg funkcji pozwalających na pracę z danymi, które reprezentują. Poniżej kilka przykładów:

var number = 10; // w tle new Number(10)
var str = 'witaj swiecie'; // w tle new String('...')
var bool = new Boolean(0); // poczatkowa wartość 'false'
var arr = new Array('jeden', 'dwa', 'trzy'); // tablica
var d = new Date(); // data ustawiona na teraz
var regex = new RegExp('witaj', 'g');

alert(str.length); // pobieranie dlugosci tekstu
alert(str.toUpperCase()) // konwersja na wielkie litery

alert(arr.length); // wielkosc tablicy
alert(arr.indexOf('dwa')); // znajdowanie indeksu elementu

d.setDate(d.getDate() + 1); // ustawianie daty
alert(d.getDate()); // pobieranie daty

alert(Math.PI); // wartosc liczby PI
alert(Math.round(1.14567)); // zaokraglanie

alert(str.match(regex)); // uzycie wyrazenia regularnego

Powyższy przykład można sobie przetestować na jsfiddle.net.

To oczywiście tylko mały wycinek dostępnych w tych obiektach funkcji - myślę, że każdy jest w stanie zapoznać się z resztą sam, w miarę potrzeb i nie ma powodu, rozpisywać się na ten temat w tym poście.

Tworzenie własnych obiektów

Podobnie jak w innych językach programowania, wszystkie obiekty, zarówno natywne jak i własne, dziedziczą z obiektu ‘Object’. Samo tworzenie obiektów odbywa się również w sposób powszechnie znany, czyli poprzez użycie słowa kluczowego ‘new’.

W JavaScript nie ma jednak definicji klasy jako takiej. Zamiast tego, można napisać funkcję, którą później wywoła się jak konstruktor, właśnie za pomocą ‘new’ (pisałem już o tym w poprzednim poście). Dla porządku, poniżej przykład:

function SomeConstructor(initialValue) {
    this.someValue = initialValue;
}

var someObject = new SomeConstructor('test');

alert(someObject.someValue);

Jak widać, na początku mamy definicję funkcji ‘someConstrutor’, którą w linii piątej wykorzystujemy jako konstruktor tworzący obiekt ‘someObject’.

W tym miejscu możemy przejść od razu do tematu definiowania właściwości obiektów w JavaScript. W przykładzie widać, że w omawianej funkcji mamy przypisanie wartości ‘initialValue’ do zmiennej ‘someValue’ - w tym momencie (pamiętając, że ‘this’ odnosi się do klasy, która wywołuje daną funkcję), w “locie” tworzona jest właściwość, która później dostępna jest w kontekście danego obiektu (patrz linia siódma w przykładzie). Poniżej jeszcze jeden przykład:

var someObject = new Object();
someObject.someValue = 'test';

alert(someObject.someValue);

Kod powyższy pokazuje, że właściwości można tworzyć również po utworzeniu obiektu (linia druga).

Znamy już sposób tworzenia właściwości, czas więc teraz na definicję metody obiektu. Robi się to w sposób analogiczny, z tą różnicą, że zamiast do nowo tworzonej zmiennej obiektu przypisuje się funkcję, a nie konkretną wartość:

function someMethod(someValue) {
    alert(someValue);
}

var someObject = new Object();
someObject.methodOne = function () {
    alert('witam z metody pierwszej!');
}
someObject.methodTwo = someMethod;

// wywolania
someObject.methodOne();
someObject.methodTwo('witam z metody drugiej!');

W powyższym przykładzie pokazane są dwa sposoby zdefiniowania metody obiektu. Sposób pierwszy - funkcja ‘inline’ (linie od szóstej do ósmej). Sposób drugi - standardowe zdefiniowanie funkcji (linie od pierwszej do trzeciej), a następnie przypisanie jej nazwy do odpowiedniej właściwości obiektu.

Wzorzec modułu

Jak słusznie zauważył w komentarzu do tego posta czytelnik Arek Bal, przy tworzeniu obiektów warto wspomnieć w tym miejscu o wzrorcu modułu.

Dotychczas dowiedzieliśmy się, że do tworzenia obiektów w JavaScript stosuje się funkcje pełniące jednocześnie rolę konstruktorów. Możliwe jest także definiowanie publicznych właściwości i metod już po utworzeniu obiektu (a także wielokrotne ich nadpisywanie). Są jednak sposoby na uzyskanie większej enkapsupalcji czyli odseparowanie właściwości i metod i uczynienie ich prywatnymi. I tutaj właśnie przychodzi nam z pomocą wzorzec modułu. Popatrzmy na taki przykład:

var Module = function (initialValue) {
    // prywatna zmienna
    var someValue = initialValue;

    // prywatna metoda
    var calculateValue = function() {
        return someValue * 2;
    }

    return {
        // publiczna metoda
        getValue : function() {
            // uzycie prywatnej metody (nie uzywamy 'this')
            return calculateValue() - 1;
        }
    }
}

var obj = new Module(10);
alert(obj.getValue());
alert(obj.someValue); // zwraca wartosc 'undefined'
alert(obj.calculateValue()); // blad

Przypadek powyższy pokazuje, w jaki sposób za pomocą wzorca modułu można zrealizować enkapsulację zmiennych i metod. W powyższym kodzie, tworzymy zmienną ‘Module’ do której przypisujemy funkcję anonimową - posłuży ona nam później jako konstruktor obiektu. Do zaimplementowania zmiennej i metody prywatnej użyte zostało słowo kluczowe ‘var’ - jak dowiedzieliśmy się we wpisie na temat zakresu zmiennych w JavaScript, tak utworzone zmienne mają zasięg tylko wewnątrz tej funkcji, nie będą więc widoczne z zewnątrz. Nasza funkcja zwraca za to obiekt anonimowy, zawierający właściwość ‘getValue’, do której przypisana zostaje funkcja anonimowa - w ten sposób implementujemy metodę publiczną (tak samo implementuje się też właściwości publiczne).

Prototypy

Dwa akapity wcześniej, wspomniałem o tworzeniu właściwości i metod - dowiedzieliśmy się, że aby je stworzyć, wystarczy dokonać operacji przypisania wartości, a właściwość lub metoda utworzy się w locie. Istnieje jednak jeszcze jedna możliwość…

Każdy obiekt w JavaScript, posiada zdefiniowaną specjalną właściwość zwaną ‘prototype’. Dzięki niej mamy możliwość definiowania prototypów klasy, tzn. możemy zadeklarować zestaw właściwości i metod, które będzie posiadał każdy obiekt (utworzony za pomocą słowa kluczowego “new”). Zobaczmy więc przykład:

function TestClass(value) {
    this.someValue = value;
}

// jak wiemy funkcja jest równiez obiektem
// wiec posiada także swój prototyp, a wiec:
testClass.prototype.anotherValue = 'wartosc';
testClass.prototype.method = function() {
    alert('funkcja method');
}

// tworzymy obiekt
var obj = new TestClass('test');

// wywolania - wlasciwosc i metoda juz zdefiniowane
alert(obj.anotherValue);
obj.method();

Najpierw oczywiście funkcja będąca też jednocześnie konstruktorem. Następnie sedno sprawy - za pomocą właściwości ‘prototype’ definiujemy właściwość i metodę. Na koniec możemy zaobserwować, że właściwość i metoda są dostępne od razu po utworzeniu obiektu.

Na temat prototypów planowałem już od dłuższego czasu napisać osobnego posta. Myślę więc, że w kontekście egzaminu 70-480, to co napisałem na temat prototypów jest wystarczające, a do tematu jeszcze wrócę w przyszłości.

Znając już zagadnienie prototypów możemy przejść do najważniejszego i jednocześnie ostatniego paragrafu tego wpisu…

Dziedziczenie

Jako że język JavaScript jest w pełni obiektowy, możemy również w pełni korzystać z jego dobrodziejstw, czyli właśnie możliwości dziedziczenia. Niestety realizacja tego zagadnienia jest trochę inna niż w językach takich jak C#. Musimy zaimplementować je sami, przy użyciu właśnie właściwości ‘prototype’.

Spójrzmy najpierw na klasę rodzica:

function Parent() {
    // nic nie rób
}

// metody klasy rodzica
Parent.prototype.getValue = function() {
    alert('metoda rodzica!!');
}
Parent.prototype.getAnotherValue = function() {
    alert('inna metoda rodzica!!');
}

Widzimy, że ma ona dwie metody. W kolejnym przykładzie stworzymy klasę dziedziczącą, która przesłoni pierwszą z nich:

function Child() {
    // nic nie rób
}

// dziedziczymy klase parent czyli
// kopiujemy jej prototyp do prototypu dziecka!
Child.prototype = new Parent();

// przeslaniamy metode
Child.prototype.getValue = function() {
    alert('wywołano mnie z dziecka!');
}

var obj = new Child();
obj.getValue();
obj.getAnotherValue();

Sprawa wygląda więc całkiem prosto - dziedziczenie polega na przypisaniu do właściwości ‘prototype’ klasy dziedziczącej obiektu klasy rodzica. W ten sposób, wszystkie prototypowe właściwości i metody rodzica, widoczne są również w klasie dziecku. Dziedziczenie takie nazywamy “dziedziczeniem prototypowym”.

Obiekty i metody w języku JavaScript - podsumowanie

To w zasadzie tyle jeśli chodzi o obiekty i metody w języku JavaScript - cały czas zastanawiam się, czy na potrzeby egzaminu 70-480, taki poziom szczegółowości jest wystarczający… Jeśli ktoś z czytelników uważa, że należało by temat poszerzyć, proszę o komentarze, jakich elementów brakuje i co należałoby jeszcze tutaj dopisać! Na pewno rozważę wszystkie propozycje ;)