Javascript jest językiem w pełni obiektowym, lecz pomimo tego nie wspiera natywnie mechanizmów dziedziczenia podczas deklaracji klas i obiektów jak robią to pozostałe języki programowania jak np. Java czy C#. Dlatego też w javascripcie mechanizm dziedziczenia trzeba zaimplementować samemu, żeby to zrobić warto znać podstawy tworzenia obiektów.
Do tworzenia obiektów/klas w javascripcie wykorzystujemy funkcje:
//deklarujemy funkcje jako konstruktor obiektu
function Parent (value) {
this.value = value;
}
//tworzymy instancje obiektu
var parentInstance = new Parent();
W javascripcie każdy obiekt posiada swój prototyp oznaczony w języku jako prototype.
Prototyp definiuje zestaw metod, pól które sa tworzone podczas każdej inicjalizacji obiektu przy pomocy słowa kluczowego new tak jak w przykładzie powyżej. Rozwijając powyższy przykład dodamy funkcje getName i getGroup do naszej klasy:
//deklarujemy funkcje jako konstruktor obiektu
function Parent (value) {
this.value = value;
}
/*
* dodajemy do prototypu funkcje getName i getGroup,
* bedą one dostępne w każdym zainicjalizowanym obiekcie
*/
Parent.prototype.getName = function(){
return 'I am parent';
};
Parent.prototype.getGroup = function(){
return 'Parent Group';
};
//tworzymy instancje obiektu
var parentInstance = new Parent();
//wywołujemy funkcje utworzonego obiektu
alert(parentInstance.getName());
alert(parentInstance.getGroup());
Mamy naszą podstawową klasę razem z dwoma metodami. Teraz chcielibyśmy zdefiniować obiekt/klasę która dziedziczy po naszym podstawowym obiekcie i przykładowo nadpisuje metodę getName aby zwrócić swoją nazwę. Tworzymy funkcje (konstruktor) obiektu Child. Ponieważ chcemy żeby każda instancja obiektu który dziedziczy posiadała wszystkie metody/pola obiektu Parent do prototypu przypisujemy nową instancje obiektu Parent oraz nadpisujemy metodę getName.
function Child() {
//konstruktor dla obiektu Child
}
/*
* Przypisujemy-dziedziczymy metody Parent-a używamy new Parent()
* żeby do prototypu przypisać nową instancje (kopie) funkcji parenta,
* w przeciwnym wypadku nadpisując metodę getName nadpisalibyśmy tez
* metodę getName w prototypie Parenta. Dzięki temu wszystkie funkcje
* klasy Parent (przy pomocy prototypu) będą dostępne we wszystkich
* instancjach klasy/obiektu Child.
*/
Child.prototype = new Parent();
//nadpisujemy metode getName
Child.prototype.getName = function () {
return 'I am Child';
};
//inicjalizujemy obiekt
var childInstance = new Child();
/*
* Sprawdzamy-wywołujemy funkcje widzimy że Child odziedziczył metodę
* getGroup oraz że metoda getName została nadpisana.
*/
alert(childInstance.getGroup()); //wyswietli 'Parent Group'
alert(childInstance.getName()); //wyswietli 'I am a Child'
Javascript jako język niestety w dziedziczonych obiektach nie wspiera metody super jak robią to inne języki np. Java, gdzie w funkcji którą nadpisujemy możemy wywołać nadpisywaną funkcje parenta. W javascripcie żeby to zrobić musimy wywołać funkcje prototypu w kontekście naszego obiektu:
Child.prototype.getName = function(){
/*
* Nie ma metody 'super' wiec wywołujemy metodę z prototypu Parenta
* w kontekście naszego obiektu poprzez użycie 'call' lub 'apply'
*/
var superParentName = Parent.prototype.getName.call(this);
return 'I am Child' + ' my parent said: ' + superParentName;
};
//tworzymy instancje obiektu Child
var childInstance = new Child();
/*
* sprawdzamy funkcje getName powinna
* wyswietlić: 'I am Child my parent said: I am Parent'
*/
alert(childInstance.getName());
Ta implementacja dziedziczenia ze względu na wykorzystanie prototypów nosi nazwę javascript prototype inheritance, można znaleźć dziesiątki przykładów podobnych to tego powyżej, w praktyce jednak warto ten przykład usprawnić o kilka elementów. Przede wszystkim warto stworzyć funkcje którą będziemy wykorzystywać do dziedziczenia obiektów, oraz poprawić przypisanie nowej instancji parent-a do prototypu naszego obiektu:
//w momencie wywolanie new Parent() obiekt Child nie posiada definicji funkcji
Child.prototype = new Parent();
dlatego iż takie przypisanie zakłada że obiekt Parent jest klasą abstrakcyjną i jego konstruktor nie przyjmuję argumentów i nie wywołuje metod które mogą lub zależnie od implementacji powinny się pojawić nadpisane w obiekcie Child. Dużo praktyczniejsza implementacja wyglądała by tak:
/*
* Ponieważ wszystko w javascripcie jest obiektem i posiada prototyp
* (funkcje też) zmodyfikujemy prototyp każdej funkcji żeby stworzyć
* metode do dziedziczenia, w tym przypadku 'this' odwołuje się do
* naszego obiektu (zamiast Child)
*/
Function.prototype.inherit = function( parentClass, extendWith){
//tworzymy funkcje tymczasową która wykorzystamy do
//skopiowania prototypów
function f(){};
/*
* do funkcji przypisujemy prototyp klasy po której dziedziczymy
* (metody i pola)
*/
f.prototype = parentClass.prototype;
/*
* przypisujemy do naszego obiektu
* kopie prototypu (nie wywołując konstruktora
* parenta tylko tymczasowa funkcje)
*/
this.prototype = new f();
/*
* poprawiamy konstruktor (inaczej wskazywałaby na
* parentClass.prototype.constructor)
*/
this.prototype.constructor = this;
/*
* jeśli jako drugi argument (opcjonalnie) został podany
* obiekt z funkcjami rozszerzającymi parenta przypisujemy je
*/
if(extendWith){
for(var key in extendWith){
this.prototype[key] = extendWith[key];
}
}
};
//deklarujemy obiekt Parent
function Parent (value) {
this.value = value;
}
/*
* definiujemy funkcje w prototypie
* (można zrobić to przy pomocy obiektu jak poniżej)
*/
Parent.prototype = {
getName: function(){
return 'Parent';
},
getValue: function(){
return this.value;
}
};
//Definiujemy konstruktor Child-a
function Child (value) {
/*
* wywolujemy konstruktor Parenta w kontekście naszego obiektu
* zeby przekazac parametry - emulacja 'super'
*/
Parent.apply(this, arguments);
}
//Wywołujemy metodę inherit którą dodaliśmy do prototypu funkcji (Function)
Child.inherit(Parent, {
//nadpisujemy metode getName
getName: function () {
//ponownie przykład emulacji 'super'
var parentName = Parent.prototype.getName();
return 'I am a Child of a ' + parentName;
}
});
//Tworzymy instancje obiektu Child
var childInstance = new Child('some value');
//sprawdzamy czy wszystko się udało
alert(childInstance.getName());
alert(childInstance.getValue());
Do pisania aplikacji obiektowo w javascripcie również można wykorzystać narzędzia udostępniane przez frameworki javascriptowe. Przykładowo MooTools posiada bardzo fajną implementację obiektowości i dziedziczenia przy pomocy funkcji Class. Ponieważ prawie wszystkie implementacje w dostępnych frameworkach są modyfikacjami powyższego podejścia (prototype inheritance), uważam że warto wiedzieć jak ono działa i jak się je implementuje dlatego że nie zawsze możemy w projekcie skorzystać z MooTools lub innych frameworków.


