Abyś lepiej zrozumiał na czym polega dziedziczenie, najpierw opiszę Ci na czym polega i jak rozwiązuje problemy w klasycznych językach obiektowych takich jak C#, C++ czy Java. Dziedziczenie bardzo łatwo można omówić na przykładzie figur geometrycznych. Załóżmy, że mamy dwie klasy: pierwsza z nich to square (kwadrat), a druga z nich to rectangle (prostokąt). Obie mają metodę setWidth() (ustawSzerokość). Metody te są proste, obie wyglądają identycznie, jedyne co robią to ustawiają jeden z boków na wartość podaną w parametrze metody. Ale co by było gdyby ta metoda miała dużo bardziej skomplikowaną logikę? No nic, zawsze można skopiować ciało metody i wkleić do tych samych metod w innej klasie :) No właśnie nie bardzo. Mając identyczne metody, każda zmiana w jednej z nich obliguje Cię do zmiany w tej drugiej i zawsze musimy o tym pamiętać. Kolejna sprawa, jeżeli w naszej metodzie będzie jakiś bug, będziemy musieli go zmieniać osobno w każdej klasie. A co jeśli wprowadzimy jeszcze kilka nowych klas i każda klasa będzie miała taką samą metodę, wprowadza to bardzo duży chaos w naszym programie. Aby rozwiązać taki problem możesz między innymi skorzystać właśnie z tytułowego dziedziczenia. To znaczy musisz wydzielić tak zwaną klasę bazową - w tym wypadku może to być klasa shape (kształt), która będzie miała deklarację metody setWidth, a klasy pochodne to znaczy squre oraz rectangle będą z niej dziedziczyć. W takim przypadku klasy pochodne będą również mogły korzystać z metody setWidth. Stosując takie dziedziczenie, chcąc zmienić metodę setWidth wystarczy to zrobić w jednym miejscu.
Wróćmy do JavaScript'u, w języku JavaScript nie ma klas, operujemy na obiektach. Dziedziczenie w JavaScript implementuje się trochę inaczej niż w językach obiektowych, ale jak przekonasz się za chwilę nie ma w tym nic skomplikowanego.
Aby zrozumieć jak stosować dziedziczenie w JavaScript musisz wiedzieć czym jest prototype. Każdy obiekt w JavaScipt zawiera coś takiego jak wspomniany wcześniej prototype. Jest to tak zwany obiekt nadrzędny. Abyś lepiej mógł to zrozumieć przyjrzyj się poniższemu zdjęciu.
Wystarczy, że wpiszesz w konsoli:
let obj = {};
obj
obj.toString();
Jak widzisz zdefiniowany przez nasz pusty obiekt obj zawiera __proto__, a w nim różne metody oraz właściwości, które nasz pusty obiekt może używać, ponieważ te metody zostały odziedziczone. Powyżej użyliśmy metody toString() chociaż ta metoda nie została przez nas nigdzie wcześniej zadeklarowana. Jeżeli wywołujesz jakąś metodę, silnik JavaScript najpierw sprawdza czy taka metoda jest zadeklarowana w obiekcie głównym, jeżeli nie ma takiej metody, to wtedy sprawdza w klasie nadrzędnej, czyli w tej z której dziedziczy obiekt główny. Takim sposobem właśnie została wywołana metoda toString(). Nie było takiej metody w klasie głównej, ale w klasie nadrzędnej już tak. Aby wyświetlić wszystkie metody i właściwości klasy nadrzędnej możesz użyć metody Object.getPrototypeOf():let obj = {};
console.log(Object.getPrototypeOf(obj));
Dziedziczenie może mieć dużo poziomów. Można to zauważyć między innymi na tablicach. Przyjrzyj się poniższemu zdjęciu.Jak widzisz zadeklarowaliśmy tym razem pustą tablicę. W obiekcie nadrzędnym każdej tablicy znajduje się mnóstwo metod, które już wcześniej używaliśmy w naszym kursie JavaScript :) Zauważ, że na samym dole jest zagnieżdżone kolejne __proto__ tym razem jest to obiekt bazowy, który widziałeś już w poprzednim przykładzie.
Ok, skoro wiesz już czym jest prototype to możemy przejść do praktyki.
function Shape(width) {
this.width = width;
}
Shape.prototype.setWidth = function(width) {
this.width = width;
}
let shape = new Shape(10);
shape.setWidth(20);
console.log(shape.width);
console.log(shape);
Za pomocą prototype możesz ustawić metodę w obiekcie nadrzędnym. Jak możesz zobaczyć na zdjęciu metoda setWidth zostałą dodana to __proto__ i o to nam chodziło.Wróćmy zatem do naszego początkowego problemu i napiszmy kod tak, aby square oraz rectangle dziedziczyły z shape. Aby ustawić prototype dla naszego obiektu musimy skorzystać ze składni:
ObiektPochodzny.prototype = Object.create(ObiektBazowy.prototype);
A zatem nasz kod będzie wyglądał w taki sposób:function Shape() {
}
Shape.prototype.setWidth = function(width) {
this.width = width;
}
function Square() {
}
function Rectangle() {
}
Square.prototype = Object.create(Shape.prototype);
Rectangle.prototype = Object.create(Shape.prototype);
let square = new Square();
let rectangle = new Rectangle();
square.setWidth(10);
rectangle.setWidth(20);
Jak widzisz wszystko działa tak jak chcieliśmy. Jeszcze jedna rzecz o której musisz pamiętać przy dziedziczeniu. Aby poprzez zastosowanie dziedziczenia nie usuwać naszego konstruktora musimy go ustawić ponownie po zresetowaniu naszego prototype. Aby to zrobić należy po zresetowaniu prototype wpisać:ObiektPochodny.prototype.constructor = ObiektPochodny;
Dodatkowo, aby nie pisać zbyt dużo powtarzającego się kodu, możemy kod odpowiedzialny za ustawienie dziedziczenia wydzielić do osobnej metody. Finalnie nasza implementacja będzie wyglądała w taki sposób:function extend(Child, Parent) {
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
}
function Shape() {
}
Shape.prototype.setWidth = function(width) {
this.width = width;
}
function Square() {
}
function Rectangle() {
}
extend(Square, Shape);
extend(Rectangle, Shape);
let square = new Square();
let rectangle = new Rectangle();
square.setWidth(10);
rectangle.setWidth(20);
To wszystko co chciałem Ci przekazać w tym artykule. Zapraszam Cię do kolejnych artykułów z serii podstaw programowania w języku JavaScript.Poprzedni artykuł - Abstrakcja w JavaScript.
Następny artykuł - EcmaScript 6 dziedziczenie w JavaScript.