w Praktyce
9 godz. 47 min · JavaScript · Full-stack i Programowanie
Piotr PalarzWeb DeveloperW pierwszym rozdziale tego kursu poznasz bardzo dokładnie koncepcje programowania zorientowanego obiektowo. W jednej z lekcji, omówimy wszystkie z nich na przykładzie pseudo-kodu, niezwiązanego bezpośrednio z językiem JavaScript. Dzięki temu zrozumiesz, jak realizowane jest programowanie obiektowe w wielu popularnych językach programowania. Dowiesz się czym jest klasa, obiekt, instancja, właściwości, metody, a także zrozumiesz takie pojęcia jak dziedziczenie, polimorfizm, abstrakcja czy enkapsulacja. Następnie przejdziemy już do języka JavaScript i do zrozumienia tego, w jaki sposób powyższe koncepcje są w nim zaimplementowane. Język ten nie jest bowiem w 100% klasycznie obiektowy, jak np. język PHP. Co jednak bardzo istotne, JavaScript implementuje tzw. dziedziczenie prototypowe, którego bardzo dogłębne zrozumienie, gwarantuje zrozumienie całego języka JavaScript. Dowiesz się zatem czym są konstruktory i jak je tworzyć, a także jak korzystać z prototypu funkcji. Dzięki temu będziemy mogli wykorzystać obiektowość do własnych celów, co później w praktycznych przykładach uczynimy.Na przestrzeni kilku lekcji zobaczysz krok po kroku, jak w języku JavaScript zaimplementować dziedziczenie wielokrotne. Taka wiedza absolutnie otwiera oczy na zależności rządzące obiektami w tym języku. Dowiesz się również jak rozszerzać wbudowane konstrukcje języka JavaScript czy Obiektowego Modelu Dokumentu, a także kiedy powinniśmy to robić. Omówimy problemy, jakie mogą się pojawiać przy korzystaniu ze słowa kluczowego this, a także sposoby ich rozwiązania. Poznasz 3 bardzo ważne metody funkcji: call, apply i bind.JavaScript ma wiele specyficznych cech, a jedną z nich są tzw. domknięcia. W jednym z rozdziałów zobaczysz, do czego one służą i kiedy powinniśmy z nich korzystać. Dopełnimy tym również wiedzę związaną z programowaniem obiektowym i tzw. prywatnością.Rozdział o obiektowości został celowo umieszczony na samym początku kursu, gdyż zrozumienie tych koncepcji pozwala patrzeć zupełnie inaczej na wszystkie pozostałe. Po tym rozdziale przejdziemy do omówienia bardzo popularnego, lekkiego formatu wymiany danych JSON. Dowiesz się jak wygląda składnia tego formatu, a także jak współpracuje on z językiem JavaScript.Następnie przejdziemy do bardzo ekscytującego tematu, jakim jest technologia AJAX. Dzięki niej możemy wysyłać zapytania do serwera i pobierać dane, bez przeładowywania witryny. Do zrozumienia AJAXa niezbędne jest jednak rozumienie działania protokołu HTTP, na którym opiera się sieć www. Z tego powodu, jedna z lekcji została w 100% poświęcona HTTP i dowiesz się z niej jak wysyłane są żądania i odpowiedzi, a także tzw. nagłówki, które będą dla nas niezwykle istotne. Chwilę później omówimy również bardzo ważne mechanizmy: Same- origin policy i CORS, które blokują możliwość wysyłania żądań AJAX do pewnych serwerów. Kiedy już będziesz rozumiał te niezwykle istotne koncepcje, przejdziemy do napisania pierwszego kodu, wysyłającego asynchroniczne żądanie do serwera. Następnie dowiesz się jak wysyłać, a także odbierać dane z serwera, umieszczać je na stronie oraz śledzić postęp zarówno wysyłania, jak i pobierania danych.W tym rozdziale na przestrzeni kilku lekcji napiszemy swoją własną bibliotekę AJAX, która pozwoli w niezwykle łatwy sposób wysyłać i odbierać dane. Skorzystamy w niej już w kolejnej lekcji, gdzie zobaczysz, jak wysłać wiadomość e-mail z użyciem AJAX i PHP po stronie serwera. Gotowy skrypt, będziesz mógł umieścić na własnej stronie internetowej. Na zakończenie tego rozdziału omówimy również dokładnie niezwykle sprytną technikę o nazwie JSONP, które pozwala wysyłać tzw. żądania “cross-origin”. Przekonasz się dlaczego taka technika powstała i jak współpraca klienta i serwera potrafi obejść zabezpieczenia przeglądarek.Kolejnym tematem omówionym w kursie będą Cookies, czyli tzw. ciasteczka. Dowiesz się czym są, w jakim celu istnieją, a także jak są ustawianie przez serwer. Co ciekawe, również z poziomu języka JavaScript będziemy mieli do nich dostęp, a zatem zobaczysz jak je ustawiać i odczytywać. Na tym etapie przyda nam się wiedza zdobyta w lekcji o protokole HTTP.W kursie znajdziesz również rozdział poświęcony w 100% wyrażeniom regularnym. To mechanizm języka, który pozwala dopasowywać tekst do definiowanych przez nas wzorców. Wyrażenia regularne potrafią być bardzo zaawansowane i odstraszać, dlatego rozłożymy je na czynniki pierwsze, byś dokładnie je zrozumiał. Na przestrzeni wielu lekcji dowiesz się jakie są specjalne symbole wykorzystywane przy wyrażeniach regularnych, czym są kwantyfikatory, kotwice czy np. grupowanie. Następnie przejdziemy do wykorzystania wyrażeń regularnych w języku JavaScript. Poznasz zarówno metody obiektu RegExp jak i te dostępne na obiekcie String. Dzięki nim będziemy mogli wyszukiwać np. adresów e- mail w podanym tekście, usuwać je lub zamieniać na coś innego. Jako praktyczny projekt, stworzymy “Antybluzgacz”. Wykorzystamy do tego celu programowanie obiektowe, a sama aplikacja będzie pozwalała filtrować wpisywany przez użytkownika tekst wiadomości i wyłapywać w nim niecenzuralne słowa. Wszystkie z nich zamienimy na przypadkową reprezentację znaków, np. $#!@%^. Ten projekt pokaże Ci nie tylko jak korzystać z wyrażeń regularnych, ale także jak profesjonalnie pracować z kodem JavaScript.W przedostatnim rozdziale tego kursu znajdziesz lekcje związane z dobrymi praktykami pracy. Omówimy tzw. “strict mode” języka JavaScript, zagadnienia związane z minifikacją i konkatenacją skryptów, sprawdzaniem poprawności pisanego kodu oraz delegacją zdarzeń DOM.Na końcu kursu znajdziesz 10 krótkich lekcji z serii Tips & Tricks, które przybliżą Ci wiele ciekawych konstrukcji języka JavaScript, a także technik na rozwiązanie różnych problemów. Zobaczysz np. jak wczytać obrazy w tle czy jak wyświetlić na pasku tytułowym witryny migające powiadomienie.Podsumowując, kurs ten jest dopełnieniem wiedzy z zakresu języka JavaScript i Obiektowego Modelu Dokumentu z kursu “JavaScript od Podstaw”. Poprzedni kurs nie jest wymagany, lecz jego przerobienie z pewnością pozwoli dużo lepiej przyswoić wiedzę z tego kursu, gdyż były one przygotowywane równocześnie. Oba te materiały, pozwolą od zera, wskoczyć na naprawdę wysoki poziom rozumienia języka, a także dobrych praktyk pisania kodu.
Do tego kursu mogą jednak przystąpić wszystkie osoby, które znają już podstawy języka HTML, CSS i JavaScript i chcą poszerzyć swoją wiedzę, a także spojrzeć zupełnie inaczej na ten język.
W tej lekcji kontynuujemy sobie poznawanie dziedziczenia wielokrotnego w języku JavaScript. Jak możemy to zrobić.
Mam nadzieję, że ze mną jesteś na bieżąco po lekcji poprzedniej, że również sobie to przepisałeś, i że rozumiesz.
Jeżeli jeszcze tego nie zrobiłeś, oczywiście możesz oglądać dalej, ale lepiej zrozumiesz, kiedy również to przepiszesz
własnymi rękoma.
Więc przejdźmy sobie teraz do kodu i zastanówmy się co do tej pory zrobiliśmy.
Mamy trzy klasy. Można powiedzieć Shape, Rectangle i Square.
Natomiast tylko Shape pozwala nam obliczać obwód czy obliczać pole. Pozostałe kasy nie. Bo tutaj jakby,
powiedzmy, dziedziczymy sobie z nich pewne rzeczy. Ale tak naprawdę tylko je pożyczamy. Pokazałem Ci jak,
za pomocą call,
możemy to zrobić. Czyli wywołując sobie taki Rectangle,
tak naprawdę pożyczamy to, co jest tutaj i zostanie to do this przypisane.
Jeżeli tworzymy Square, to pożyczamy to od Rectangle, a on pożycza od Shape. Czyli tak naprawdę fizycznie wpisujemy
w Square this.
Dokładnie to samo co mamy tutaj.
Czyli to jest takie pożyczenie. Natomiast jest to technika o tyle ciekawa, że zapewniam cię, iż jeżeli
to zrozumiałeś, to już wpisujesz się w dosyć wąskie grono, tak przynajmniej wynika z mojego doświadczenia,
osób, które o tym wiedzą i to rozumieją. Więc jest to taka technika wcale nie dla początkujących.
Ja natomiast chcę ci o niej powiedzieć, aby pokazać ci jak programowanie obiektowe, takie klasyczne, które starałem się
w pierwszej lekcji tego kursu wytłumaczyć, przełożyć na JavaScript. Więc teraz sobie to będziemy kontynuować.
Teraz pokażę ci rzecz ciekawą.
Może cię ona zaskoczy, może nie.
Natomiast mamy np. Rectangle. Bo najpierw będziemy chcieli, aby on dziedziczył Shape.
Jeżeli wpiszemy sobie tutaj Rectangle.prototype,
to pamiętasz, być może, że jest tam konstruktor i to jest w zasadzie wszystko, co tam się znajduje. Bo proto to jest
to taka ukryta właściwość. Więc konstruktor tylko tam się znajduje i jest to zwykły obiekt.
Mówiłem ci, że do niego można różne rzeczy dopisywać. Ale być może się domyślasz, że skoro to jest zwykła
właściwość, zwykły obiekt to mogę napisać Rectangle.prototype i nadpisać go pustym obiektem. Jak najbardziej
możemy to zrobić. W tym momencie nadpisaliśmy go pustym obiektem. Natomiast podobnie moglibyśmy
zrobić z Shape.
Jeżeli ja pisałem Shape.prototype to tam się odwoływaliśmy do obiektu kropka getArea. To tam dodawałem. Gdybym napisał Shape,
w ten sposób, prototype, to mógłbym tutaj napisać getArea, w ten sposób, i funkcja.
Dokładnie zrobiłbym to samo co tutaj.
Czyli możemy sobie obiekt prototype, który jest utworzony, jak stworzymy funkcję. Możemy go nadpisać czymś
innym.
Niemniej ja bym chciał tutaj wrócić do Rectangle i pokazać ci jaki jest problem. W ten sposób. Jeżeli prototyp,
który automatycznie tutaj się utworzył. Był to obiekt, który miał tylko właściwość konstruktor, która kierowała
tu. Jeżeli go nadpiszemy pustym obiektem,
on tak wyglądał. Odświeżę i raz jeszcze to wpiszę, to teraz mamy pusty obiekt. I co prawda możemy do niego
dodawać różne metody, różne rzeczy, inne również właściwości. Ale gdyby jakiś programista korzystający
z naszego kodu z biblioteki, chciał sprawdzić jakiej klasy jest to obiekt, to już nie będzie tego mógł
zrobić. Aby to naprawić wystarczy, że tutaj, albo w tym miejscu wewnątrz tego obiektu, od razu albo odwołując
się do niego, w ten sposób, dodamy ręcznie właściwość konstruktor, która tam była, w ten sposób. I on ma
kierować na Rectangle. Czyli tutaj podajemy Rectangle. Pamiętaj, że nie w cudzysłowach. To nie jest String, to
po prostu podstawiamy tu referencje do tej funkcji.
Jeżeli teraz wrócę, odświeżę, raz jeszcze sobie na to zerkniemy, to zauważ, że ten obiekt jakby jest klasy Rectangle
tylko dlatego, że dodaliśmy tutaj właściwość konstruktor, która kieruje na naszą funkcję. Czyli w tym momencie
zrobiliśmy to samo, co było dla nas zrobione wcześniej automatycznie. Więc mógłbyś zapytać o co halo.
Chodzi generalnie o to, że teraz stworzyliśmy całkiem nowy obiekt z prototypem. Bo mówiłem ci, że prototyp
to jest taki zwykły obiekt.
Stąd właśnie on też dziedziczy z object.prototype.
Natomiast teraz mógłbyś sobie pomyśleć, to na razie wykomentuje, że jeżeli chcielibyśmy aby nowe, utworzone
obiekty Rectangle, może tu utwórzmy Rectangle. Pamiętaj, że tu musimy podać tablicę. Bo tak sobie to wymyśliliśmy.
Z dwoma bokami, do Rectangle, to jest tutaj i ona potem będzie przekazana do Shape i sobie to pożyczymy. Aby
w this zapisać tablicę z czterema elementami, aby taki nowy obiekt shape1 teraz miał dostęp do tego co Shape,
to moglibyśmy napisać rectangle równa się shape.prototype, prawda? Czyli Rectangle.prototype będzie kierować
na to samo co Shape.prototype.
Zerknijmy czy to ma sens i utworzyliśmy nowy obiekt shape1, ale on został utworzony,
pamiętaj, z Rectangle i sprawdźmy czy on teraz będzie miał teraz taką metodę jak np. getPerimeter.
Okazuje się, że ona tutaj jest i działa.
To jest ciekawe, że ona dobrze działa.
Czyli w tym momencie wydawałoby się wszystko jest OK. Zróbmy teraz rzecz kolejną
i przejdźmy niżej.
Skoro mamy tutaj Square, to może byśmy napisali sobie powiedzmy Square.prototype = Shape.prototype. To możemy
to zrobić w ten sam sposób. Albo napisać Rectangle.prototype. Bo to będzie dokładnie to samo.
Rectangle.prototype kieruje tam, gdzie Shape.prototype, ale dla nas będzie tak bardziej widoczne, że Shape dziedziczy z Rectangle.
Chociaż w rzeczy samej
wszystkie te obiekty będą kierować dokładnie tutaj.
Ale spróbujmy to zrobić. Napiszemy Square.
Tutaj z kolei wymyśliłem sobie, że w przypadku Square podajemy nie tablice, ale tylko, w ten sposób, długość
boku.
Więc również nam to powinno zadziałać i shape1.
To teraz będzie Square, czyli kwadrat.
Napiszmy mu getPerimeter, 80.
Czyli wszystko jest OK.
Teraz pomyślałbyś sobie "No fajnie, zrobiliśmy sobie tak naprawdę dziedziczenie". Bo wszystkie te figury z siebie
dziedziczą. Tutaj sobie pożyczały pewne właściwości, zapisujemy je na stałe, a prototyp jest dziedziczony.
No to teraz zrobimy sobie trójkąt i zobaczymy jak to będzie działać w przypadku trójkąta. Czyli znowu taką
funkcję jak mieliśmy wcześniej. Nazwę ją Triangle. Tutaj chciałabym żeby było sideLengths, żeby podawać długości
boków.
Założymy, że jest to trójkąt równoboczny. Dla naszego przykładu. To znowu będzie tablica i będziemy chcieli
dziedziczyć Shape.
Dlaczego tak, to pokazywałem ci na górze. Triangle dziedziczy shape, Rectangle też,
a Square dziedziczy tutaj i tutaj.
Mam nadzieję, że to jest jasne.
I teraz gdzie mieliśmy ten trójkąt.
Tutaj. Chcę sobie znowu pożyczyć stamtąd, ale nie musimy tutaj tego przekazywać, w taki oto sposób, chociaż
w zasadzie, jeżeli jest to trójkąt równoboczny, OK, to też będzie sideLength. Czyli tak samo będziemy podawać
jak tutaj. Ok. Ale, że ma trzy boki, no to będziemy chcieli przekazać do naszego Shape to trzykrotnie.
Czyli taka tablica będzie np. 20, 20, 20 i ona w this zostanie zapisana właśnie dlatego, że tutaj to robimy.
Okej, powinno być dobrze.
Mamy Triangle. Oczywiście zmienimy sobie jego nazwę na "Trójkąt".
Te nazwy jeszcze wykorzystamy. Ja ci to pokażę w jednej z kolejnej lekcji.
Mamy sobie trójkąt. Utwórzmy nowy trójkąt. New Triangle, długość boku 20. Ok, czyli shape1 to jest teraz trójkąt, getPerimeter nie działa.
Jeszcze oczywiście przypiszmy sobie tutaj prototyp. Będzie
kierował na Shape.
Chociaż gdybym napisał Rectangle, przypominam, to dokładnie ten sam obiekt.
Ok, teraz po odświeżeniu shape1.getPerimeter powinno zadziałać. Bo się nam nie odświeżyło.
Raz jeszcze. Coś nam nie zadziałało.
Oczywiście to musimy napisać zanim utworzymy taką figurę.
Jak widzisz tutaj ją utworzyliśmy.
Nie mieliśmy jeszcze dostępu do tego prototypu.
Zróbmy sobie to w tym miejscu.
Ok.
Teraz nam to powinno zadziałać.
To było z miejscu, działa.
Czyli mamy trójkąt i teraz przejdziemy do clue.
Tego wszystkiego o co chodzi.
Po pierwsze nie jest to prawdziwe dziedziczenie.
Powiem Ci z jakiego powodu.
W zasadzie możemy sobie teraz sprawdzić czy utworzony trójkąt,
Triangle, czy on jest tej samej instancji, co klasa. Czyli pokazywałem ci już operator instanceof
czyli instanceof Shape. Wpisałem Triangle.
Oczywiście chodziło mi o shape1. Czyli kształt, który został utworzony. Mamy true. Czyli shape1
jest jakby instancją klasy Shape.
Wydawałoby się w porządku. Bo jakby dziedziczymy z niej. Sprawdźmy czy shape1 jest instancją klasy Triangle. True.
Wydawałoby się że, również jest ok. Więc teoretycznie dziedziczymy.
Dlaczego tak jest.
Chcę ci teraz powiedzieć.
Otóż dlatego, że mają one ten sam prototyp.
Prototyp kierujący w to samo miejsce. Natomiast potem ci pokażę, że tak być wcale nie musi. Tylko teraz
pojawia się pewien problem.
Jeżeli chciałbym obliczyć pole naszego trójkąta, czyli tutaj mamy getArea, to nie możemy pomnożyć jednego
boku przez drugi. Bo podawaliśmy tam np. 20, 20, 20. To jest trójkąt równoboczny. 20 razy 20 nie da nam
pola takiego trójkąta. Na to jest inny wzór. Więc chcielibyśmy ten wzór sobie zmienić i teraz możliwości jest
kilka.
Pierwsza z nich jest taka, że tutaj na sztywno zapiszemy this.getArea i to będzie funkcja, ja ci od razu
wkleję tutaj tę funkcję. Będzie tam prosty wzór.
W ogóle się nie martw, że jest tutaj coś skomplikowanego.
To jest bardzo prosty wzór. Czyli a kwadrat, a razy a, pierwiastek z trzech, czyli math.sqrt, dzielone na cztery.
I tutaj robię jeszcze toFixed(2).
Z tego powodu, że chciałbym, aby tutaj były dwa miejsca po przecinku zapisane. W zasadzie to ja ci to pokaże,
abyś miał pewność skąd to się wzięło. A zmiennej a zapisuje this.sideLength[0]. Bo pamiętasz, że to była tablica.
Choć tutaj przekazaliśmy 20, to w this zostało zapisane dzięki Shape coś takiego.
Wszystkie są takie same więc tylko 0 sobie zapisuje. Czyli pierwszy z nich w zmiennej a, aby tutaj było po prostu
krócej.
Ok, to będzie nam działać. Więc pokażę ci jak to wygląda.
Jeżeli trochę chaosu się wkradło to przepraszam, shape1.getArea, OK.
Mamy poprawne pole. Tylko, że chciałem go skrócić. To jest number, gdybyśmy sprawdzili typeof.
Chciałem go skrócić. Więc tutaj napisałem na końcu toFixed(2). Bo mówiłem ci, że na liczbach możemy z takiej funkcji
korzystać. W pierwszej części kursu pokazywałem, co jest znowu ciekawe.
Tak musisz o tym myśleć. To tutaj zostanie zwrócony number. Prymitywny.
Jeżeli skorzystamy z toFixed(2), zostanie zamieniony na object. Czyli na obiekt number i w jego prototypie znajdzie
się toFixed. Tylko z metodą toFixed jest taki problem, że ona zwraca String z dwoma miejscami po przecinku. Bo
pisałem tam dwójkę. Aby String skonwertować na liczbę to albo go przepuszczamy przez number, albo wystarczy,
że na samym początku dodamy plus.
Więc to jest taki trik. Stąd się to wzięło.
Czyli teraz jeżeli napiszemy Shape, w ten sposób, to z dwoma miejscami po przecinku zostanie nam on zwrócony.
I pomyślałbyś "wow".
Już jest wszystko tak, jak być powinno.
Niestety tak nie jest. Bo teraz jeżeli utworzymy kolejny trójkąt, to zauważ, że ta funkcja jest na sztywno
wpisana. Ona przesłoniła co prawda funkcje z prototypu. Czyli funkcję getArea, a więc jeżeli na shape1
wywołujemy getArea, to jest sprawdzane czy bezpośrednio w tym obiekcie taka funkcja jest. Jest. Więc jest
w porządku. A w przypadku kwadratu czy prostokąta będzie tutaj dziedziczona funkcja z prototypu.
Ale teraz za każdym razem kiedy sobie stworzymy nowy obiekt trójkąt, a załóżmy że piszemy jakąś grę i
tutaj będzie więcej logiki, będziemy takie trójkąty tworzyć, takie figury, to będziemy tę funkcję powtarzać w
pamięci.
No więc już wiesz, co powinniśmy zrobić.
Przypiszmy sobie ją do prototypu. Czyli zamiast wpisywać ją tam bezpośrednio,
przejdźmy do Triangle.prototype.getArea, no i zobaczmy czy nam to zadziała.
Nie ma jej już bezpośrednio. Więc możemy sprawdzić. Sekunda.
Źle wpisałem, ok. Odświeżam tutaj i sprawdźmy czy nam to wszystko dobrze wyliczy.
Wyliczyło świetnie, jest OK.
Natomiast co stanie się teraz. Jeżeli zamiast Triangle utworzymy sobie Square, czyli kwadrat. Utworzyłem kwadrat
i sprawdźmy, co nam zostanie zwrócone.
Ups, coś chyba nie tak, prawda?
Kwadrat, któremu tutaj podałem 20, to powinniśmy mieć pole 20 razy 20, czyli 400. A tutaj zostało zwrócone
coś innego.
Mam nadzieję, że wiesz co się stało. W tym momencie jeżeli napisałem Triangle.prototype.getArea, a Triangle.prototype
kierował na Shape.prototype, to tak naprawdę nadpisaliśmy cały prototyp, który był tutaj. W zasadzie niecały
prototyp ale getArea. A my nie chcemy tego robić. Bo z tego prototypu dziedziczy sobie Rectangle czy np. Square.
Dlatego, że tutaj to przypisaliśmy. Czyli w ten sposób nam to niestety nie zadziała. Bo to nadpisaliśmy, a my chcielibyśmy
zrobić to w ten sposób, aby trójkąt dziedziczył Shape, metodę getPerimeter, bo ona jest taka sama, czyli oblicz obwód, ale
aby miał własną metodę getArea. No i w tym przypadku pomyślałbyś sobie, że rzeczywiście jedyna możliwość
to wpisanie tego tutaj na sztywno tak, jak to pokazałem przed momentem. Ale wcale tak nie jest i możemy
sobie z tym poradzić. Pokażę ci w jakiś sposób. Przejdźmy sobie tutaj troszkę wyżej i zaczniemy tak naprawdę.
w ogóle od Rectangle. Czyli zamiast w przypadku Rectangle mówić, że jego prototyp chcemy żeby to był Shape.prototype,
to byłoby dużo lepiej, gdyby tu był obiekt, do którego bez żadnego problemu, możemy jeśli chcemy, w przypadku
Rectangle nie trzeba, ale w przypadku trójkąta chcemy do jego prototype, tak jak tutaj coś dodać, aby to był obiekt, ale aby
ten obiekt dalej sobie dziedziczył z Shape.prototype.
Czy w ogóle jest to możliwe?
Okazuje się, że tak.
Jeśli odwołamy się do Object.create, jest taka specjalna funkcja. Object.create.
Ona tworzy obiekt.
Jeżeli natomiast przekażemy jej tutaj obiekt, to stanie się prototypem utworzonego obiektu.
Mam nadzieję, że to jest w miarę zrozumiałe. Jeszcze za sekundę to wytłumaczę.
Czyli jeżeli ja tutaj powiem, że Rectangle.prototype to ma być Object.creare.Shape.prototype,
to tak jakbym powiedział "Ja chcę żeby to był nowy obiekt", ale jeżeli coś w tym obiekcie nie zostanie
znalezione, to chcę żeby jego prototyp. To było to co przekazałem tutaj.
Jeżeli tak zrobimy, Rectangle.prototype = Object.create, to teraz nie pisaliśmy do Rectangle.prototype Shape.prototype. Przypisaliśmy
nowy obiekt, którego prototypem jest Shape.prototype.
Zaraz ci to pokaże, jak to jest zobrazowane.
I teraz jeszcze będziemy chcieli sobie ten konstruktor, o którym mówiłem również przypisać. Zrobimy dokładnie
to samo ze Square. Object.create. Ale żeby on właśnie nie dziedziczył Shape tylko z Rectangle, to zrobimy w ten sposób.
Czyli tutaj Rectangle.prototype
To będzie nowy obiekt, który dziedziczy z Shape.prototype.
Tutaj utworzymy nowy, który dziedziczy z Rectangle.prototype, a on później dziedziczy jeszcze z Shape.prototype.
Tutaj sobie też piszemy konstruktor. Bo to jest ważne. Jeżeli nadpisujemy taki obiekt, to jest to o czym ci
mówiłem, to konstruktor chcemy tutaj ustawić na Square. W ten sposób. I zróbmy to samo tutaj z trójkątem. Czyli
Shape.prototype chcemy wstawić w ten sposób.
I tutaj dodamy sobie również konstruktor, w ten oto sposób. Teraz sprawdźmy czy to wszystko działa. W zasadzie
to nazwijmy sobie tutaj tę figurę square. OK.
Utwórzmy sobie nową, którą nazwiemy triangle. Podajemy to w ten sam sposób, jak pamiętasz. I utwórzmy nową. Bo samej Shape
nie będziemy tworzyć. Bo nie wiadomo jaka jest to figura. Ale utwórzmy taką, którą nazwiemy prostokąt. Czyli
to będzie rectangle.
Piszę to z małej litery. Wiedz, że gdybym napisał z dużej, to byśmy nadpisali to co mieliśmy wcześniej.
Nie możemy tak zrobić. Bo funkcja tak się nazywa. Więc z małej to piszemy. new Rectangle.
Tutaj musimy już podać tablicę, jak pamiętasz, długości boków. Pierwszy i drugi. To będzie 20 i 10.
I teraz jeżeli sobie tutaj przejdziemy, odwołały się do square. Square i np. getPerimeter, 80. Jest OK. Zróbmy
to samo na rectangle, 60.
W porządku i zróbmy to samo na trójkącie.
Na naszym obiekcie jest ok. Natomiast teraz jeżeli się odwołamy np. do square.getArea to podaliśmy tutaj
20.
Czyli to powinno być 400.
Zgadza się.
A zróbmy to samo na naszym trójkącie.
Jak widzisz, teraz wszystko jest w porządku. Teraz wszystko zostało zwrócone tak, jak być powinno. I jak
musimy to prześledzić raz jeszcze. Może najlepiej będzie, jeśli tutaj odświeżę. Kiedy odwołamy się do
poszczególnych klas.
Jeżeli wpiszemy sobie teraz tutaj Rectangle.prototype, to będziemy mieli prototyp, w którym nic nie ma. Bo nic tam nie
dodawaliśmy, jak pamiętasz.
Jeżeli przejdziemy do jego proto, do tego proto, tego obiektu, to to będzie Shape i okaże się, że w nim mamy getArea
i getPerimeter.
Z tego właśnie powodu Rectangle.prototype to jest pusty obiekt, którego proto jest ustawiony na Shape, że
napisaliśmy tutaj Rectangle.prototype = Object.create, czyli "Stwórz nowy, pusty obiekt". Tutaj do niego przypisaliśmy
konstruktor.
Co widzisz w tym miejscu. Czyli stwórz nowy, pusty obiekt, ale jego proto ustaw na Shape.prototype.
Dzięki temu tak to działa. Zupełnie podobnie będzie to wyglądać w przypadku Square. Chociaż jeden poziom
niżej. Square.prototype będzie miał konstruktor. Square. Jego proto kieruję na Rectangle.
Dlaczego.
Bo tutaj napisałem Square.prototype, stwórz nowy obiekt i chciałbym żeby jego proto kierowało na Rectangle.
Więc tak też się dzieje. Kieruje na Rectangle.prototype, a Rectangle.prototype kieruje na Shape. I mam nadzieję,
że to teraz dobrze widzisz.
I ostatnia rzecz. Jeżeli odwołamy się do Triangle, to ona będzie dziedziczyć tak jak Rectangle. Czyli mamy
w nim jakieś elementy i jego proto kieruje prosto na Shape.
Ale teraz zauważ, że Triangle.prototype ma w sobie zapisane getArea. Czyli nadpisaliśmy funkcję, która tam
była.
Square w swoim prototypem, czyli tutaj, nie ma getArea, a więc sobie je dziedziczy
właśnie z Shape, które jest dopiero w tym miejscu. Ale Triangle ma getArea w tym miejscu i teraz w
taki sposób, wygląda hierarchia naszych prototypów.
Jak widzisz, na samej górze mamy object.prototype, z niego dziedziczy nasze Shape.prototype.
Pod Shape.prototype
Mamy Triangle.prototype, które dziedziczy Shape.
Obok mamy Rectangle, które również z tego dziedziczy.
One są jakby na tym samym poziomie. A na samym dole mamy Square.
Tak wygląda właśnie dziedziczenie wielokrotne.
I teraz mógłbyś zapytać, po co właściwie ci to pokazuję. Czy często będzie to dla ciebie przydatne.
Niekoniecznie.
W swoich programach jeżeli już teraz zacząłeś się tym martwić, kiedy ty wymyślisz, że trzeba to w ten sposób
zrobić. W swoich programach nie za często będziesz z tego korzystał.
Natomiast chciałem, aby ten kurs był kursem innym niż wszystkie. Aby omawiał jak naprawdę działa JavaScript
i jak działa dziedziczenie prototypowe.
I również chciałem tutaj pokazać przykład takiego dziedziczenia.
Natomiast jeżeli uczyłeś się już innych języków programowania, albo kiedyś będziesz się ich uczył to
będziesz miał świetne porównanie do JavaSkript.
Jak to wygląda w języku programowania, gdzie jest klasyczny model programowania obiektowego, a jak to
wygląda w JavaScript. Czyli jak możemy tu zaimplementowane takie dziedziczenie wielokrotne.
Wracamy sobie jeszcze tutaj i pewnie to już rozumiesz, ale chciałbym jeszcze tylko raz powiedzieć. Jeżeli do
Triangle, tutaj, odwołamy się do getArea, to jest sprawdzane czy na tym obiekcie bezpośrednio jest to
przypisane. Nie jest. Bo musielibyśmy tutaj nadpisać Triangle i przypisać. Wchodzimy do prototypu Triangle,
który znajduje się tutaj. I w nim jest już to przypisane. Bo to zrobiliśmy. Więc jest zwracane.
Czyli teraz nadpisaliśmy metodę, która byłaby sprawdzana dalej. Jeżeli tu by jej nie było,
przeszliśbyśmy do kolejnego prototypu, czyli do Shape.prototype.
Tam jest getArea, zostałoby zwrócone.
Jeżeli tam by nie było, to przeszliśbyśmy do Object.prototype.
Gdyby tam nie zostało znalezione, to zostałoby zwrócone do undefined.
Na sam koniec pokażę ci jeszcze, że jeżeli tutaj będziemy chcieli napisać Triangle, czyli nasz utworzony obiekt,
jest to instancja, instaceof
Shape. Okaże się, że to jest prawda i w językach programowania zorientowanych obiektowo
tak być powinno.
Jeżeli zapytamy czy instanceof Rectangle, to nie jest to prawdą. A jeżeli Triangle, jest to prawdą. Podobnie jeżeli zrobimy sobie
tutaj na przykład Square. Square instace of Triangle, to nieprawda i wszystko się zgadza. Ale Square. W zasadzie
źle zrobiłem. Bo to powinno być z małej litery.
Ale tak czy siak zostanie zwrócone false.
Tutaj będziemy mieli true. Bo nasz obiekt square jest właśnie obiektem klasy jakby Square. Ale, że dziedziczy z Rectangle,
to również tutaj będzie true i podobnie będzie w przypadku Shape.
Teraz już całkowicie kończąc tę dosyć długo lekcję, chciałbym ci tylko przypomnieć co mówiliśmy i jak
to się ma do tego co pokazywałem w pierwszej lekcji tego kursu odnośnie koncepcji programowania obiektowego.
Bo omówiliśmy tutaj praktycznie wszystko i możemy z innych języków programowania obiektowych,
przełożyć to na nie do końca w stu procentach, klasycznie obiektowy język JavaScript. Bo jak widzisz,
tutaj nie mamy klas. Ja o funkcjach mówiłem jako o klasach, ale to nie do końca jest tak. Więc teraz wkleje tutaj
taki komentarz i wygląda to następująco. Mówiliśmy o klasach więc z taką klasą, czyli takim szablonem obiektów,
które będą tworzone, jest w naszym przypadku funkcja np. klasa Shape czy klasa Square itd.
Następnie obiekt, czyli instancja w języku programowania jest to utworzony z klasy obiekt. Czyli ten obiekt,
ten obiekt i ten obiekt. Następnie właściwości i metody, to już na tym etapie wiesz, że właściwość to są
jakieś dane zapisane w obiekcie, a metody to są funkcje, które na obiektach można wykonywać. Takie jak
np. getPerimeter. Następnie konstruktor. Mówiłem ci, że jest to specjalna funkcja, która się wywołuje wtedy,
kiedy tworzymy z klasy nowy obiekt. W języku JavaScript konstruktorem jest cała taka funkcja. Bo to ona
się wywołuje, to do niej możemy przekazywać parametry. Jak robimy na przykład tutaj. Czyli cała taka funkcja
jest konstruktorem.
Nie musimy tutaj dodawać, gdzieś specjalnie takiej funkcji.
Dziedziczenie mam nadzieję, że już rozumiesz, polega na tym, że jeżeli do ogólnej klasy figury dodaliśmy
"oblicz pole" czy "oblicz obwód" to inne mogą z tego dziedziczyć.
I chociaż nie przepisaliśmy tego ponownie do trójkąta czy np. do prostokąta, to on to dziedziczy.
I w fajny sposób to działa.
Abstrakcja to jest taki termin nie techniczny, ale polegający właśnie np. na tym, że stworzyliśmy w przypadku
Triangle metodę getArea, która nie korzysta z tej metody, która była wcześniej dla innych figur.
Ma własną metodę i dzięki temu dla nas działa to abstrakcyjne. W tym sensie, że jeżeli utworzymy dowolny
obiekt, którejkolwiek z tych klas to wiemy, że możemy się do niego odwołać
kropka getArea i zawsze dla nas to zostanie poprawnie zwrócone.
Abstrakcją również tutaj jest fakt, że pozwoliłem na stworzenie obiektów takich jak np. Square, gdzie nie
musimy podawać wszystkich czterech boków. Bo skoro wiemy, że one są równe, to nie musimy ich podawać w
ten sam sposób. Ale w konsekwencji jeżeli zajrzysz do Square_sideLengths, tam będzie tablica z czterema
takimi elementami.
Więc to jest również abstrakcyjne.
Teraz mamy polimorfizm i to jest już to, o czym mówiłem. To się łączy z abstrakcją. Polimorfyzm
polega na tym, że mamy różne figury i każda z nich ma metodę pod tytułem getArea. Ale w przypadku trójkąta
jest ona całkiem inaczej tutaj realizowana.
Tu jest zupełnie inny wzór.
Ale chodzi generalnie o ten sam efekt. Aby zwrócone zostało dla nas pole figury. Więc to jest polimorfizm.
Czyli taki obiekt dostosował się do warunków i odpowiednio wszystko zwraca.
I na końcu jeszcze mamy coś takiego jak enkapsulacja. Oddzieliłem to tutaj jednym enterem.
Z tego powodu, że w JavaScript nie tak łatwo jest zrobić klasyczną enkapsulację.
Chodzi mi tutaj o to, że moglibyśmy, tak jak ci wspominałem, napisać private przy sideLengths, czy przy innych tutaj
właściwościach i nie byłoby do nich dostępu.
Natomiast teraz jeżeli przejdziemy do przeglądarki i napiszemy na przykład Triangle_sideLengths,
to możemy odwołać się do tej tablicy.
Ja natomiast wolałbym, aby nie można było tego zrobić i wtedy nazywalibyśmy to enkapsulacją.
Natomiast poprzez metodę np. getPerimeter czy getArea możemy się do tego odwołać. Bo ona za naszymi plecami
tak naprawdę to wszystko zalicza.
Gdybyśmy chcieli z kolei, aby móc się odwołać do sideLengths, moglibyśmy stworzyć metodę getsideLengths i w niej zrobilibyśmy
return, właśnie coś takiego.
Czyli enkapsulacja byłaby wtedy, gdybyśmy mogli to ukryć.
A jak widzisz można się do tego odwołać.
Więc zrobiliśmy to z podkreślnikiem, aby innym programistom powiedzieć, że nie powinni tego w żaden sposób
przypisywać. Bo oczywiście gdyby zrobili tak, to mogli by to zmienić.
O enkapsulacji jeszcze w tym kursie sobie powiemy.