Dziedziczenie w Javascript

Jak to działa w Javascript?

Do tej pory używaliśmy różnych typów danych. Poza null i undefined, każdy z typów pozwalał nam wykonywać na nich jakieś operacje. Przykładowo dla tekstów mogliśmy pobrać ich długość, pobrać ich wycinek czy zwrócić ich wersję pisaną dużymi literami. Dla tablic mogliśmy dodawać czy odejmować elementy za pomocą odpowiednich metod, a dla liczb przykładowo mogliśmy sformatować daną liczbę do odpowiedniej ilości miejsc po przecinku.

Skąd te wszystkie funkcjonalności się wzięły?

Programowanie obiektowe charakteryzuje się pewnymi mechanizmami. Jednym z podstawowych jest tak zwane dziedziczenie.

Gdybym zapytał ciebie czym jest dziedziczenie w realnym świecie co byś powiedział?

Dzieci dziedziczą po rodzicach pewne cechy. Może to kolor włosów, może kolor oczu, a może talent do rysowania.

Część takich właściwości sobie pobierają od rodziców, ale i też mają swoje własne.

Tata był przystojny. Syn też jest. Ale syn jest o wiele wyższy od ojca. Córka po mamie odziedziczyła spojrzenie, ale dodała swoje wyjątkowe różowe policzki.

Niektóre właściwości też nadpisują, bo na przykład córka ma włosy rude po rodzicu, ale jakieś takie nie za bardzo.

Co ważne w drugą stronę to nie działa. Rodzice nigdy nie dziedziczą po dzieciach (w realnym świecie czasami tak, ale to temat dla prawników).


To może inny przykład? Wyobraź sobie, że robimy grę - np. podobną do R-Type. My - jako statek lecimy w prawą stronę, z której nadlatuje na nas stado przeciwników.
Każdy taki przeciwnik powinien mieć właściwości takie jak pozycję, szybkość, swoją grafikę, siłę ataku plus wiele, wiele innych. Dodatkowo powinien mieć metody takie jak latanie, zniszczenie itp. Takich przeciwników chcielibyśmy mieć kilkanaście typów. Wszystkie te typy powinny mieć podobne funkcjonalności, ale równocześnie też każdy taki typ powinien dodawać coś od siebie. Jedni by strzelali, drudzy latali ale szybciej, inni byli by bardzo wytrzymali.
Oczywiście moglibyśmy tutaj zastosować słynną technikę każdego programisty czyli copy-pasta, ale było by to bardzo niewygodne dla nas. Wyobraź sobie, że napisałeś już funkcjonalność latania, skopiowałeś do 10 innych typów obiektów, po czym okazało się, że na samym początku popełniłeś jakąś literówkę. Cała zabawa od początku.
I tu właśnie przychodzi z pomocą dziedziczenie. Zamiast głupio kopiować, możemy sobie utworzyć typ Enemy, a następnie inne typy jak EnemyStrong, EnemyFast, EnemyShoot itp, które będą dziedziczyć po tamtym podstawowe funkcjonalności przeciwnika, a i będą mogły dodać lub zmienić dane funkcjonalności.

dziedziczenie obiektów

Dziedziczenie w Javascript

Podobnie jest także w Javascript. Gdy tworzysz jakiś typ danych, możesz dla niego odpalać różne funkcjonalności. Takie możliwości nie biorą się z nieba, a są właśnie dziedziczone.

W Javascript występuje tak zwane dziedziczenie prototypowe. Oznacza to, że każdy* obiekt dziedziczy właściwości i metody z innego obiektu.

Stworzymy prosty obiekt i zbadajmy go w konsoli:


const cat = {
    name : "Konczenti"
}

console.dir(cat);

Gdy to zrobisz poza właściwością name, zobaczysz w tym obiekcie dodatkową właściwość __proto__.
Jest to właściwość dodawana przez JavaScript każdemu obiektowi (z malutkimi wyjątkami, które umyślnie teraz pominę).

Właściwość ta wskazuje na inny obiekt (tak zwany prototyp), z którego nasz obiekt dziedziczy.


const cat = {
    name : "Konczenti"
}

console.dir(cat);
console.log( cat.toString() ); //[object Object]

Zauważ, że w powyższym kodzie dla naszego kota odpaliłem metodę, której on nie ma. Jeżeli cokolwiek używamy dla danego obiektu, JavaScript początkowo szuka tego bezpośrednio w danym obiekcie. Jeżeli tego nie znajdzie, korzystając z właściwości __proto__ przechodzi do prototypu i tam próbuje tej rzeczy znaleźć i użyć.
Prototyp obiektu także jest obiektem, więc też dostał swoją właściwość __proto__, dzięki której w razie potrzeby Javascript może przejść do kolejnego obiektu. Sytuacja taka będzie się powtarzać, aż do momentu w którym Javascript odnajdzie daną metodę lub właściwość, lub dojdzie do ostatniego obiektu w hierarchii, który już swojego __proto__ nie ma.

Możesz to spokojnie sprawdzić w konsoli rozwijając kolejne poziomy naszego kota.

Większość typów danych możemy tworzyć za pomocą literałów, ale też za pomocą tak zwanych konstruktorów.


const obA = {}
const obB = new Object();

const boolA = true;
const boolB = new Boolean(true);

const tabA = ["ala", "bala"];
const tabB = new Array("ala", "bala");

const txtA = "Ala ma Konczenti";
const txtB = new String("Ala ma Konczenti");

Każdy taki byt jest konkretnego typu. Gdy utworzymy obiekty danego typu - np. Array, __proto__ takiego obiektu wskazuje na obiekt-prototyp danego typu obiektów na który wskazuje też właściwość prototype znajdująca się w danym konstruktorze. Taki obiekt zawiera zbiór właściwości i metod, z których obiekty danego typu mogą korzystać. Dzięki temu tablice mogą używać metod tablicowych (np. push(), pop()), teksty odpowiednich metody dla stringów (np. toUpperCase()) itp.


const tab = [1,2,3];
tab.__proto__ === Array.prototype //true
tab.push === Array.prototype.push //true

const txt = "Ala ma kota";
txt.__proto__ === String.prototype //true
txt.toUpperCase === String.prototype.toUpperCase //true

Gdy tworzysz prosty obiekt za pomocą literału (const cat = {}), on także jest jakiegoś typu - jest obiektem. Jego konstruktorem jest więc Object, a właściwość __proto__ wskazuje na prototyp mieszczący się w konstruktorze Object.


const ob = {};
ob.__proto__ === Object.prototype //true

To właśnie ten mały obiekt-prototyp jest domyślnie na samym szczycie hierarchii dziedziczenia, co oznacza, że każdy typ danych jaki stworzysz w Javascript część rzeczy dziedziczy od niego.

Dodatkowo obiekt ten nie ma właściwości __proto__ więc Javascript na nim właśnie kończy wędrówkę rzucając błędem albo w ostatniej chwili odpalając szukaną rzecz.

Można więc powiedzieć, że całe to prototypowe dziedziczenie przypomina swoistą siatkę zależności, w której kolejne obiekty są ze sobą połączone łańcuchem prototypów.

Mega siatka połączeń
Zobacz w powiększeniu

Powyżej odnosiłem się do wbudowanych typów. My jako programiści możemy też oczywiście tworzyć nasze własne typy, które mają swoje własne metody i właściwości. Zajmiemy się tym w kolejnych rozdziałach.

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-obiekty

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu