Kurs obiektowego JavaScript (25)

Autor: Damian Chodorek • Opublikowany: 14 października 2014 • Ostatnia aktualizacja: 8 lutego 2015 • Kategoria: javascript, kursy

Wstęp do dziedziczenia w JS.

W JS dziedziczenie można zaimplementować na wiele sposobów. Wynika to ze specyficznych własności tego języka. W C++ czy Java używamy specjalnej konstrukcji, która mówi, że dana klasa ma dziedziczyć po innej. W JS nie ma klas. Jest natomiast pole prototype i to przy jego użyciu zachodzi dziedziczenie.

Dziedziczenie łańcuchowe

To pierwszy i domyślny sposób dziedziczenia. Opisany jest w standardzie ECMAScript. Poniżej przykład hierarchii.

function Shape(){
  this.name='shape';
  this.getName=function(){ return this.name; };
}

function Shape2D(){
  this.name='2D shape';
}

function Triangle(){
  this.name='Triangle';
}

//oto sposób w jaki należy wykonać dziedziczenie:
Shape2D.prototype=new Shape();
Triangle.prototype=new Shape2D();

Zauważ, że dziedziczenie nie jest bezpośrednie. Odbywa się ono przy użyciu operatora new, który tworzy nowy obiekt. Po takiej operacji Shape2D() lub Shape() można zmodyfikować lub całkowicie usunąć i nie wpłynie to na obiekt dziedziczący oraz jego własności.

Być może pamiętasz z poprzedniej lekcji, że zastąpienie obiektu prototype nowym, ma niezbyt pożądane konsekwencje. Więc pamiętaj zawsze zresetować także konstruktor.

Shape2D.prototype.constructor=Shape2D;
Triangle.prototype.constructor=Triangle;

var sh=new Triangle();
sh.name; //"Triangle"
sh.getName(); //"Triangle", metoda odziedziczona po Shape

Oto sposób w jaki znaleziono metodę getName():

  • silnik JS sprawdza czy istnieje ona w obiekcie sh,
  • nie została tam odnaleziona, dlatego zaczyna sprawdzać w obiekcie do którego odnosi się prototype konstruktora, tym obiektem jest instancja Shape2D(),
  • również tam nie odnaleziono metody, przeszukiwany jest więc obiekt prototype konstruktora Shape2D() czyli Shape(),
  • znaleziono metodę, zostaje więc wywołana z this odnoszącym się do obiektu sh.

Warto zauważyć jak zadziała operator instanceof i metoda isPrototypeOf().

sh instanceof Triangle; //true;
sh instanceof Shape2D; //true;
sh instanceof Shape; //true;
sh instanceof Array; //false;

Triangle.prototype.isPrototypeOf(sh); //true
Shape2D.prototype.isPrototypeOf(sh); //true
Shape.prototype.isPrototypeOf(sh); //true
Array.prototype.isPrototypeOf(sh); //false

Przeniesienie wspólnych pól do prototype

W przypadku, który przedstawiłem powyżej jest jedna mała wada związana z wydajnością. Za każdym razem kiedy stworzysz obiekt używając new Shape(), pole name oraz getName będzie stworzone w pamięci na nowo. Oczywiście dzieje się tak, ponieważ te pola zostały utworzone przy użyciu this.

Rozwiązaniem tego problemu jest dodanie pól, które powinny być współdzielone, do prototype. W szczególności powinno być to zastosowane do metod. Poniżej ulepszony kod. Obydwa pola zostaną przeniesione.

function Shape(){}
Shape.prototype.name="Shape";
Shape.prototype.getName=function(){ return this.name; };

function Shape2D(){}
Shape2D.prototype=new Shape();
Shape2D.prototype.constructor=Shape2D;
Shape2D.prototype.name="Shape2D";

/*Do Triangle dodamy metodę getArea oraz dwa pola: side, height. Metoda powinna być jedna dla wszystkich obiektów Triangle, ale pola powinny być oddzielne.*/
function Triangle(side, height){
  this.side=side;
  this.height=height;
}

Triangle.prototype=new Shape2D();
Triangle.prototype.constructor=Triangle;
Triangle.prototype.name="Triangle";
Triangle.prototype.getArea=function(){ return this.side*this.height/2; };

var sh=new Triangle(5, 10);
sh.getArea(); //25
sh.getName(); //"Triangle"

Dziedziczenie tylko prototype

Skoro cały kod, który tak naprawdę się liczy w dziedziczeniu, zawarty jest w prototype, to warto nie używać new Shape() do dziedziczenia, ale Shape.prototype. Po pierwsze nie będą tworzone zbędne elementy z Shape(), po drugie skróci to przeszukiwanie obiektów.

function Shape(){}
Shape.prototype.name="Shape";
Shape.prototype.getName=function(){ return this.name; };

function Shape2D(){}
Shape2D.prototype=Shape.prototype; //TU ZASZŁA ZMIANA
Shape2D.prototype.constructor=Shape2D;
Shape2D.prototype.name="Shape2D";


function Triangle(side, height){
  this.side=side;
  this.height=height;
}

Triangle.prototype=Shape2D.prototype; //TU ZASZŁA ZMIANA
Triangle.prototype.constructor=Triangle;
Triangle.prototype.getArea=function(){ return this.side*this.height/2; };

var sh=new Triangle(5, 10);
sh.getArea(); //25

Czy domyślasz się jaki problem jest z powyższym sposobem dziedziczenia? Skoro wszystkie obiekty pokazują na ten sam obiekt prototype, to jego zmiana w dziecku, powoduje także zmianę w rodzicach.

Triangle.prototype.name="Triangle";
var sh2=new Shape();
sh2.name; //"Triangle" TUTAJ JEST PROBLEM

Konstruktor tymczasowy

Rozwiązaniem problemu, który pojawił się w powyższym przykładzie, jest zastosowanie konstruktora tymczasowego, który sprawi, że zmiana prototype w dziecku nie wpłynie na przodków.

function Shape(){}
Shape.prototype.name="Shape";
Shape.prototype.getName=function(){ return this.name; };

function Shape2D(){}
var F=function(){}; //TYMCZASOWY KONSTRUKTOR
F.prototype=Shape.prototype;
Shape2D.prototype=new F();
Shape2D.prototype.constructor=Shape2D;
Shape2D.prototype.name="Shape2D";


function Triangle(side, height){
  this.side=side;
  this.height=height;
}
var F=function(){}; //TYMCZASOWY KONSTRUKTOR
F.prototype=Shape2D.prototype;
Triangle.prototype=new F();
Triangle.prototype.constructor=Triangle;
Triangle.prototype.getArea=function(){ return this.side*this.height/2; };
Triangle.name="Triangle"; //wcześniej było problemem

var sh=new Triangle(5, 10);
sh.getArea(); //25

var sh2=new Shape();
sh2.name; //"Shape" TERAZ JEST OK

Ważna informacja o prototype

Być może zauważyłeś pewną niejasność. Duża liczba kursów JS w internecie nie tłumaczy tej kwestii. Ty ją jednak za chwilę poznasz i zrozumiesz. Otóż chodzi o to, że pola w prototype są kopiowane do tworzonych obiektów, a nie pobierane na bieżąco. Jeszcze dokładniej kwestię wyjaśnia poniższy przykład.

function Shape(){ this.name="Shape"; };

function Shape2D(){};
Shape2D.prototype=new Shape(); //obiekt Shape jest prototypem Shape2D

var sh=new Shape2D();
var sh2=new Shape2D();

sh.name; //"Shape"
sh2.name; //"Shape"

sh.name="NEW SHAPE"; /*Tutaj jest ważny moment. Wartość "NEW SHAPE" nie zostanie przypisana bezpośrednio do prototypu, który posiada konstruktor Shape2D, ale do kopii pola name w obiekcie sh.*/

sh.name; //"NEW SHAPE"
sh2.name; //"Shape" ta wartość nie zmieniła się

W tym artykule to wszystko. Przejdź do następnej części:

Kurs obiektowego JavaScript (26)

Zobacz również
Kurs obiektowego JavaScript (23)Wyliczeniowe pola obiektów.
Kurs obiektowego JavaScript (24)Rozszerzanie funkcjonalności obiektów wbudowanych.
Kurs obiektowego JavaScript (26)Dostęp do klasy nadrzędnej. Więcej sposobów dziedziczenia.
Kurs obiektowego JavaScript (27)Jak dołączyć kod JS do strony HTML? Poznaj modele BOM i DOM.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.