DziedziczenieJavascript 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.

Skomentuj