Archiwum

Posty oznaczone ‘programowanie’

Obiektowy Javascript cz.1. – obiekt Twoim przyjacielem

Luty 6th, 2010 reinmar Brak komentarzy

GąsienicaOpublikowane na licencji CC przez Trufflepig.

Do tego czteroczęściowego (jak zapowiem, to może napiszę :) artykułu natchnęło mnie szkolenie z zaawansowanego JavaScriptu które, w miniony weekend, zorganizował we Wrocławiu Damian Ferrante Wielgosik. Już na początku chciałbym mu podziękować za wiedzę, którą się podzielił, ponieważ przed szkoleniem byłbym wstanie napisać tylko część tego artykułu.

JavaScript to dziwoląg

Tego nie da się ukryć. JavaScript należy do wąskiej grupy języków z rodziny ECMAScriptu (JS, ActionScript, E4X), którą cechuje to, że ich obiektowość oparta jest na prototypach. Oznacza to, że w JavaScriptcie nie istnieje pojęcie klasy. Nie oznacza to jednak tego, że w JavaScriptcie nie da się programować obiektowo z wykorzystaniem np. konkretyzacji, dziedziczenia, czy właściwości prywatnych. Da się. Co więcej — da się uzyskać dużo więcej funkcjonalności klasycznego języka obiektowego, choć, o czym na sam koniec, niekoniecznie jest po co.

Obiekt

W języku z obiektowością klasową wypada zacząć od definicji klasy. Tak przynajmniej zaczynali autorzy książek, które czytałem. W przypadku JavaScriptu mielibyśmy jednak pewien problem, bo klas tutaj nie ma. Trzeba więc zacząć od utworzenia obiektu. Oto najprostszy:

var obj = {};
typeof obj; // -> "object"

Tak, to w JavaScriptcie jest najprostszy obiekt. Zadeklarowany w przykładzie jest pusty i dziedziczy po obiekcie Object. Możemy sprawdzić to za pomocą poniższego kodu (jak działa i co to znaczy, o tym później):

var obj = {};
obj.__count__; // -> 0, działa tylko w Firefoksie

obj.__proto__.constructor; // -> Object()

Obiekt możemy również utworzyć za pomocą konstruktora Object(). Nie jest to jednak sposób polecany, ponieważ… zmienną Object można nadpisać. Lepiej pozostać przy literałach obiektów, które są bezpieczniejsze i ładniejsze.

var obj = new Object(); // -> Object

Object = 5;
var obj2 = Object(); // ->  TypeError: Object is not a constructor

Właściwości obiektu

Nic nam jednak z pustego obiektu. Pora dodać do niego dane i metody. Możemy skorzystać ze składni literału obiektu:

var obj = {
	text: 'Jestem obiektem',

	saySth: function () {
		alert(this.text);
	}

};

Bądź stworzyć pusty obiekt i dodać mu właściwości:

var obj = {};
obj.text = 'Jestem obiektem';

obj.saySth = function () {
	alert(this.text);
};

W obu przypadkach efekt będzie ten sam. Kiedy wywołamy obj.saySth(); dostaniemy alerta z tekstem Jestem obiektem.

Jeszcze słowo wtrącenia o literałach. Trzeba uważać na przecinek po ostatniej właściwości. Firefox i chyba wszystkie normalne przeglądarki trzymają się specyfikacji ECMAScript 5 pkt 11.1.5 (bądź to specyfikacja trzyma się tych przeglądarek ;), a kochany IE6, który zawsze musi być inny („inny nie znaczy gorszy” w tym wypadku nie działa ;) trzyma się specyfikacji ECMAScript 3 pkt 11.1.5. Tak więc przecinek po ostatniej właściwości zostanie zaakceptowany przez przeglądarki z rodziny normalnych, zaś IE6 wywali cichaczem błąd. Nie wiem co prawda jak sprawa wygląda w IE7 i IE8, ale radzę po prostu tego przecinka nie stawiać. Koniec dygresji.

W Javie, czy PHP metoda obiektu jest czymś zupełnie innym niż jego właściwość. W JavaScriptcie metoda jest po prostu funkcją przypisaną do właściwości obiektu. Widać to najlepiej w trzeciej linii ostatniego przykładu: obj.saySth = function () {};. Wynika to z tego, że w JavaScriptcie funkcja też jest obiektem. Dzięki temu możemy ją przypisywać do zmiennych, zwracać w innych funkcjach, czy… wywoływać na niej metody. Ta właściwość JavaScriptu jest naprawdę potężna i myślę, że język ten sporo teraz zyskał w oczach osób lubiących Rubiego, Pythona, czy jakiś język funkcyjny :).

Uwaga na this

typeof obj.saySth; // -> "function"
var sayNothing = obj.saySth;

typeof sayNothing; // -> "function"
sayNothing(); // -> alert undefined

Z pierwszych trzech linii widać, że możemy sobie dowolnie operować funkcją. Czwarta może być zaskoczeniem. Dlaczego this.text === undefined? Zauważcie, że funkcja sayNothing nie została wywołana w kontekście obiektu obj, tylko w kontekście… no właśnie — czego? Żeby to sprawdzić dodajmy dwie linie kodu do poprzednich przykładów:

window.text = 'Jestem globalnym scopem';
sayNothing(); // -> alert "Jestem globalnym scopem"

Teraz widać, że funkcja sayNothing(); wywołana jest w globalnym kontekście. Możemy ją także wywołać w odpowiednim kontekście używając metody call() działającej na funkcji (tak jak pisałem wcześniej — funkcja też jest obiektem, więc ma też swoje metody):

sayNothing.call(obj); // -> alert "Jestem obiektem"

Więcej na ten temat u Ferrante w „this” w JavaScript.

Jeszcze trochę o obiektach

Do właściwości obiektu możemy się również dostać za pomocą operatora [], czyli w taki sposób jak do elementów tablicy. Jest to przydatne kiedy chcemy uzyskać właściwość, której nazwa trzymana jest w zmiennej:

var obj = { a: 'A', b: 'B', m: function () { return 'M'; } };

 
obj['a']; // -> "A"
var name = 'b';

obj[b]; // -> "B"
obj['m'](); // -> "M"

Po właściwościach obiektu można też iterować za pomocą konstrukcji for (i in obj). Trzeba jednak uważać, bo, w zależności od tego jakim obiektem dysponujemy, możemy dostać niespodziewane (przynajmniej na razie) rezultaty. Póki co jednak wystarczy nam:

var obj = { a: 'A', b: 'B', c: 'C', d: 'D' };

for (i in obj) {
	alert('obj[' + i + '] = ' + obj[i]);

}

Obiekty można też zagłębiać. Jest to własność oczywista, ale wypada dla jasności o niej wspomnieć.

var obj = {
	obj: {
		obj: {

			hidden: 'Zonk'
		}
	}
};
obj.obj.obj.hidden; // -> "Zonk"

Co dalej?

Samym obiektem programista żyć nie może. Klas jednak w JavaScripcie nie uświadczymy, trzeba więc wymyślić coś innego. Z pomocą przyjdą nam konstruktory obiektów, ale zrobią to dopiero w następnej części :).

Poza tym w trzeciej części mam zamiar napisać o prototypach, czyli JavaScriptowym dziedziczeniu. W czwartej chciałbym zebrać wszystko do kupy, pokazać może jakieś wzorce i napisać kilka uwag o programowaniu w JavaScriptcie. W zasadzie to uwagi mam już napisane, bo zacząłem od końca :D

Artykuł został także opublikowany na moim prywatnym blogu.