Techniki Zaawansowane
2 godz. 35 min · Typescript · Full-stack i Programowanie
Adam GospodarczykZacznijmy od tego, że JavaScript jest dynamicznie typowany. Oznacza to, że typy zmiennych mogą się zmieniać w czasie wykonywania programu, a my nie mamy nad nimi kontroli. A po co nam ta kontrola? Między innymi po to, aby przypadkowo nie dodać typu number do string lub przekazać do funkcji nieprawidłowe danych. Ogromną zaletą jest to, że o tego typu błędach dowiadujemy się już na etapie pisania kodu a nie dopiero w trakcie jego wykonywania... na produkcji.
TypeScript umożliwia stosunkowo prostą migrację istniejących projektów JavaScript. W kursie na przykładzie zostały omówione trzy strategie, dzięki którym jesteśmy w stanie dodać statyczne typowanie do istniejącego kodu. W procesie migracji najważniejsze jest to, że przepisywanie kodu jesteśmy w stanie uzależnić również od poziomu naszej wiedzy na temat TypeScriptu i stopniowo dodawać kolejne elementy.
Na pierwszy rzut oka można uznać, że TypeScript tylko dodaje nam pracy, wymuszając dodatkową konfigurację oraz opisywanie typów. Mało tego zwiększa barierę wejścia w projekt dla mniej doświadczonych programistów. Trudno temu zaprzeczyć ALE - korzyści płynące ze statycznego typowania które wpływają na zmniejszenie liczby błędów oraz wykrywanie ich nawet na poziomie pisania kodu, oszczędzają czas który normalnie poświęcilibyśmy na późniejsze debugowanie aplikacji. Poza tym zwiększenie stabilności aplikacji daje szerokie korzyści biznesowe, które wielokrotnie wynagradzają nam czas wymagany na pisanie dodatkowej składni.
JavaScript jest językiem dynamicznie typowanym. W trakcie wykonywania programu, zmienne mogą zmieniać swój typ a my nie zawsze mamy nad tym świadomą kontrolę. Tylko ... po co nam ta kontrola? Główną zaletą statycznego typowania jest to aby uniknąć sytuacji w których przypadkowo przypisujemy tekst do zmiennej która powinna zawierać liczbę lub sytuacji w której przekazujemy do funkcji nieprawidłowe dane. Taki rodzaj pomyłek generuje błędy odniesienia (eng. ReferenceError), które zatrzymują działanie skryptu i są momentami bardzo uciążliwe do wykrycia. Dzięki statycznemu typowaniu, jesteśmy w stanie wykrywać je na etapie pisania kodu.
TypeScript na przestrzeni ostatnich lat zyskuje ogromną popularność w społeczności programistów JavaScript i coraz częściej jest nieodłącznym elementem ofert o pracę. W przypadku firm nastawionych na rozwój rozbudowanych aplikacji JavaScript już teraz jest w zasadzie standardem. Biorąc pod uwagę fakt, że najnowsze frameworki (np. Nest.js) wykorzystują TypeScript domyślnie, nauka tej technologii ma ogromny sens w kontekście rozwoju jako programista. I ostatnim argumentem za tym aby wziąć TypeScript na poważnie jest to, że stoi za nim firma Microsoft a on sam rozwijany jest już niemal 10 lat.
Ten kurs został stworzony z myślą o programistach, którzy podstawy TypeScriptu mają już za sobą i chcą poznać jego zaawansowane techniki. Aby w pełni wykorzystać wiedzę z tego kursu, niezbędna jest również zaawansowana znajomość JavaScriptu a w szczególności jego elementów prowadzonych w wersji ES6+. Zdecydowanie polecamy przerobienie kursu Podstawowego TypeScript, który znajdziesz na eduweb, przed korzystaniem z tego materiału.
3.9+
Zaawansowaną naukę TypeScriptu zaczniemy od
powtórzenia sobie jak działa this w JavaScripcie.
Bo jak być może wcale nie jest to takie oczywiste.
Dlatego teraz przejdziemy szybko przez najczęściej występujące przypadki.
Podstawową rzeczą jest fakt że domyślnie
słowo kluczowe this wskazuje na obiekt globalny.
W tym momencie wywołuję tutaj funkcję bez jakiegoś konkretnego kontekstu.
Dlatego też faktycznie mamy tutaj global object.
Tutaj mamy tego dowód.
Natomiast obecnie praktycznie w każdym przypadku JavaScript piszemy w tzw.
trybie strict. I jeżeli go teraz aktywuję to nasze słowo kluczowe this zmieni
wartość na undefined. Tak naprawdę jest to jedna z zalet zastosowania trybu strict.
Ze względu na to że są przypadki że słowo kluczowe this lubi łapać nam ten globalny
obiekt i w takiej sytuacji gdy piszemy do właściwości tego this, czyli globalnego
obiektu jakąś wartość to zostanie ona przypisana do tego globalnego obiektu.
I to być może również wiesz ale przechowywanie informacji w globalnym
obiekcie niekoniecznie jest dobrym pomysłem.
Tak czy inaczej pamiętaj że domyślne słowo kluczowe this wskazuje na obiekt globalny.
Drugim scenariuszem w którym słowo
kluczowe this przyjmuje inną wartość jest wywołanie funkcji z wykorzystaniem słowa
kluczowego new. Czyli zdefiniujemy tutaj funkcję wewnątrz której podejrzymy wartość słowa
kluczowego this. Następnie tę funkcję wywołam
słowem kluczowym new.
No i otrzymujemy informację że aktualnie
słowo kluczowe this, wskazuje na obiekt withNew. Czyli dokładnie na instancję obiektu,
który powstał w wyniku wywołania tej funkcji jako konstruktor.
Dlatego też druga zasada brzmi w momencie gdy wywołuje funkcję jako
konstruktor czyli w taki sposób słowo kluczowe this bezpośrednio wewnątrz tego
obiektu wskazuje bezpośrednio na ten nowo utworzony obiekt.
Sytuacja wygląda całkiem podobnie w momencie gdy mamy do czynienia z metodami.
Czyli jeżeli utworzę tutaj nową klasę wewnątrz której definiuje metodę zwracającą
this no to po utworzeniu obiektu na podstawie tej klasy a następnie wywołaniu
jego metody otrzymamy wartość wskazującą bezpośrednio na ten obiekt.
Zatem zwykło się mówić że w momencie gdy wywołuje metodę słowo kluczowe this
przyjmuje wartość obiektu który znajduje się po prawej stronie tej kropki.
W tym momencie ma to oczywiście
zastosowanie ale znowu są od tego pewne wyjątki. Chociażby pierwszym z nich jest
fakt że jeżeli piszemy teraz tę metodę do nowej zmiennej a następnie wykonamy tę
funkcję w taki sposób no to jak myślisz jaki będzie efekt.
Okazuje się że otrzymaliśmy tutaj wartość undefined.
No ale dlaczego skoro dosłownie linijkę
wcześniej w momencie wywołania funkcji otrzymaliśmy odpowiednią wartość.
Otóż taką główną zasadą która stoi za przypisywaniem kontekstu do słowa
kluczowego this jest fakt że wskazuje ono na miejsce w którym została wywołana
funkcja czyli w tym momencie wywołuje tę metodę w kontekście globalnym.
Dlatego też tutaj otrzymaliśmy wartość undefined,
ale tylko z powodu że nadal mamy włączony tryb strict.
Bez niego otrzymali byśmy tutaj globalny obiekt. Natomiast linijkę wyżej wywoływaliśmy
tę metodę w kontekście obiektu newObject.
Dlatego też słowo kluczowe this wskazywało wtedy właśnie na niego.
I teraz problem w tym wszystkim polega na
tym że na tym pułapki wcale się nie kończą.
Mianowicie wróćmy teraz do naszego poprzedniego przykładu czyli wywołania w
funkcji bezpośrednio jako metody dzięki czemu znowu otrzymamy tutaj nasz
obiekt i spróbujmy nieco skomplikować sprawę.
Załóżmy że wykorzystujemy tutaj zewnętrzną
bibliotekę która oferuje nam dostęp do funkcji przyjmującej callback.
Dla uproszczenia wszystko co będzie to ta
funkcja to po prostu zwracać wywołany callback zatem wywołany tą funkcję
wewnątrz naszej metody i swoją drogą od razu zwrócę wynik a jako callback
przekaże funkcję która będzie zwracać słowo kluczowe this.
Więc swoją drogą w tym miejscu nie będzie nam ono już potrzebne.
Jak widzisz naszą funkcję oraz jej call back wykonujemy wewnątrz naszej klasy, więc
wszystko wskazuje na to że tutaj słowo kluczowe wskazuje na nasz obiekt.
No ale prawda okazuje się być zupełnie inna.
Znowu otrzymujemy tutaj undefined
a wynika to z tego że zgubiliśmy tutaj po drodze nasz kontekst.
W końcu nie wywołaliśmy tutaj funkcji słowem
kluczowym new, ani też nie wywołali jej jako metody.
Została ona po prostu wykonana w tym miejscu.
I dlaczego tak się stało wyjaśnię ci na końcu tego filmu.
Tymczasem do rozwiązania tego problemu
możesz wykorzystać wiedzę którą masz na temat funkcji strzałkowych. Funkcje
strzałkowe charakteryzują się m.in. tym że nie tworzą wiązania kontekstu tylko
słowo kluczowe this wewnątrz nich pobiera wartość z otaczającego kontekstu.
Czyli jeżeli zamienia ten callback na funkcję strzałkową.
No to w efekcie otrzymamy to czego oczekiwaliśmy.
Alternatywnym sposobem który był stosowany
przez bardzo długi czas było przypisanie po prostu kontekstu do
jakiejś innej zmiennej. Dzięki czemu efekt był dokładnie taki sam.
Niezależnie od tego czy wywołaliśmy tę funkcję w taki czy inny sposób.
Natomiast teraz już tak nie musimy robić.
No i w ten sposób zbliżyliśmy się już do ostatniej reguły, która rządzi
przypisywaniem wartości do słowa kluczowego this.
A konkretnie jest to opcja samodzielnego
bindowania tej wartości. Czyli stworzymy sobie tutaj jakąś funkcję zwracają
this a następnie jakiś zupełnie losowy obiekt.
Wiemy już że jeżeli po prostu wywołał bym
tą funkcję this wskazywałoby na globalny obiekt natomiast mogę wykorzystać tutaj
metody takie jak bind do tego aby określić kontekst tej funkcji.
W efekcie w momencie gdy po prostu wywołam tę funkcję wartość na którą wskazuje słowo
kluczowe this wskazuje aktualnie na nasz obiekt.
Ostatecznie jednak na tym nasze możliwości się nie kończą.
Do dyspozycji mamy jeszcze metody takie jak call oraz apply.
Są to metody które podobnie jak bind umożliwiają nam tworzenie kontekstu.
Natomiast różnią się tym że metoda
bind zwraca nam nową funkcję natomiast call oraz apply natychmiast ją wykonują.
Z kolei różnica pomiędzy call oraz apply polega na sposobie w jaki przyjmują
argumenty które są przekazywane do tej funkcji.
Metoda apply oczekuje że przekażemy je wewnątrz tablicy.
Natomiast metoda call oczekuje od nas, że przekażemy je po prostu po przecinku.
W każdym razie w tym momencie wiesz już w
jaki sposób bindowane jest słowo kluczowe this.
Natomiast musisz przyznać że jest to
niesamowicie zagmatwane nawet na tak prostych przykładach. A w momencie gdy mamy do
czynienia z bardziej rozbudowanym kodem no to sytuacja komplikuje się jeszcze
bardziej. Ale ostatecznie mam dla Ciebie genialne rozwiązanie.
Okazuje się że zasady rządzące tym w jaki
sposób bindowane jest słowo kluczowe this, wcale nie są takie trudne.
Mało tego sprowadzają się do pytania które już padło w tym odcinku i brzmi ono jaki
obiekt wywołał daną funkcję. Aby sobie na nie odpowiedzieć
mamy cztery pytania pomocnicze.
Pierwsze z nich to czy funkcja została wykonana słowem kluczowym new.
Jeżeli tak to this wskazuje na tę funkcję.
Drugie pytanie brzmi czy funkcja została
wykonana wykorzystując metodę call, apply lub bind.
Jeżeli tak to słowo kluczowe this wskazuje
na wartość która została przekazana za ich pośrednictwem.
Trzecim pytaniem pomocniczymi jest to czy funkcja została wykonana jako metoda.
Jeżeli tak to this wskazuje bezpośrednio na ten obiekt.
I ostatnie czwarte pytanie to czy funkcja została wykonana w globalnym zakresie.
I tutaj jeżeli faktycznie tak się stało i
mamy aktywny tryb strict no to wartość będzie ustawiona na undefined.
W przeciwnym razie otrzymamy globalny obiekt window.
I teraz możesz zadać pytanie.
No dobra ale co w sytuacji gdy kilka z tych reguł ma miejsce.
No to w takiej sytuacji wygrywa ta reguła która jest wyżej na tej liście.
Czyli jeżeli na przykład wykonujemy metodę ale bindujemy jej kontekst np.
metodą bind no to wygrywa ta reguła.
Z kolei jeżeli funkcja jest wywoływana w
globalnym zakresie ale do jej wywołania wykorzystamy słowo kluczowe new.
No to właśnie ta pierwsza reguła ma tutaj zastosowanie.
Mam nadzieję że jeżeli słowo kluczowe this
miało przed Tobą jeszcze jakieś tajemnice to w tej chwili się to zmieniło i dzięki
temu masz całą wiedzę którą będziemy potrzebować w kolejnych filmach.
Do usłyszenia w następnej lekcji już za chwilę.
SimpleCall · 1 min
"use strict"
// Default
function simpleCall() {
return (this.foo = "global")
}
simpleCall() /*?*/Constructor · 2 min
// Constructor
function withNew() {
this /*?*/
}
new withNew()
function outsideTheClass(cb) {
return cb()
}Method call · 3 min
// Method call
class newClass {
method() {
return outsideTheClass(() => {
return this
})
}
}
const newObj = new newClass()
newObj.method() /*?*/Custom call · 6 min
// custom
function customThis() {
return this
}
const obj = { x: 1 }
const bounded = customThis.bind(obj)
bounded() /*?*/
customThis.call(obj) /*?*/
customThis.apply(obj, []) /*?*/