Miniatura artykułu

JavaScript - this: odkrywanie tajemnic this

13 minut

Skopiuj link

Data publikacji: 2/9/2024, 12:23:59

Ostatnia aktualizacja: 4/19/2024

Jeśli słowo this sprawia, że przechodzą Ci ciarki po plecach, to nie martw się. W tym artykule postaramy się temu zaradzić.

Kontekst – na pewno wiele razy słyszałeś, że słowo this jest zależne od kontekstu wykonania. Prawdopodobnie niewiele daje to do zrozumienia, czym będzie this w kolejnej napisanej przez Ciebie funkcji. Spokojnie, nie jesteś sam.

Czym jest to magiczne słowo?

this jest specjalną zmienną, która jest tworzona za każdym razem, gdy funkcja jest wywoływana w JavaScript. W momencie uruchomienia funkcji silnik JavaScript decyduje, co będzie się kryło pod tą zmienną. Ten mechanizm nazywa się zakresem dynamicznym (ang. dynamic scope), co oznacza, że wartość zmiennej this jest ustalana w trakcie działania kodu, a nie na podstawie miejsca, w którym funkcja została zdefiniowana (w odróżnieniu od zakresu statycznego, ang. lexical scope). Informacje na temat zakresu statycznego w JavaScript znajdziesz w tym artykule.

W trakcie procesu przypisywania wartości do this silnik JavaScript musi wykonać szereg operacji, zanim zdecyduje się co umieścić w naszej magicznej zmiennej. Jesteśmy w stanie stwierdzić, jaka wartość zostanie umiejscowiona, odpowiadając na poniższe pytania: 

  1. Jaki typ funkcji został wywołany? 

  2. Jak funkcja została wywołana?

  3. Czy używamy trybu ścisłego? (ang. strict mode)

Dlaczego akurat te pytania? Zapraszam do lektury! 

Funkcja strzałkowa i this

Jeśli myślałeś, że funkcja strzałkowa (ang. arrow function) to tylko inny składniowy sposób definiowania funkcji, to jestem zmuszony Cię rozczarować. Jest kilka różnic w stosunku do standardowej funkcji, ale w tym artykule skupimy się wyłącznie na tych związanych z użyciem słowa this.

To specyficzny rodzaj funkcji w JavaScript. Być może natknąłeś się na stwierdzenie, że funkcja strzałkowa nie posiada słowa kluczowego this. Wydaje się to prawdziwe, jednak w rzeczywistości this wewnątrz niej działa w trybie statycznym. Co oznacza, że zawartość this jest określana na podstawie miejsca, w którym dana funkcja została zdefiniowana, podobnie jak w przypadku zwykłych zmiennych.

W związku z tym, że funkcja strzałkowa zachowuje się taki sposób, to jest ona obarczona pewnymi ograniczeniami: 

  • Nie możemy jej używać jako funkcji konstruktora 

  • Nie możemy związać this za pomocą metod call, apply oraz bind 

  • Nie zaleca się używać jej jako metody 

Jeśli wiemy, że nasza wywołana funkcja jest funkcją strzałkową i nie zmienia wartości this w trakcie wywołania, to teraz możemy zastanowić się, co znajduje się pod this w zewnętrznej funkcji, gdzie funkcja strzałkowa została zadeklarowana. 

Sposoby wywoływania funkcji

Nadszedł czas na najbardziej obszerny punkt tego artykułu. Sposób, w jaki nasza funkcja została wywołana w naszym kodzie, jest najważniejszym czynnikiem decydującym o tym, co zostanie przypisane do this.

Istnieje wiele sposobów wywołania funkcji. Zacznijmy od ich wylistowania, a następnie postaram się wyjaśnić, dlaczego tak się dzieje.

Sposoby wywołania funkcji:

  • Wywołanie bezpośrednie

  • Wywołanie jako metoda

  • Wywołanie za pomocą metod call i apply

  • Wywołanie z użyciem słowa new

  • Wywołanie pośrednie (callback)

Chciałbym zaznaczyć, że kolejne przykłady kodu, które przedstawie są w trybie ścisłym.

Wywołanie bezpośrednie

Najprostszy sposób, którego zapewne wielokrotnie używałeś. Funkcję wywołuje się bezpośrednio, podając jej nazwę oraz ewentualne argumenty w nawiasach okrągłych.

Nie ma potrzeby dużo opisywać. Deklarujemy standardową funkcję, która na konsoli wyświetla wartość this. Prawie zawsze w takim przypadku wynikiem będzie undefined.

Wywołanie jako metoda

Aby użyć funkcji jako metody obiektu, należy najpierw przypisać tę funkcję do obiektu jako jedną z jego właściwości. Po przypisaniu funkcję tę można wywołać jako metodę obiektu, używając notacji z kropką, czyli obiekt.metoda(), lub alternatywnie, dla większej elastyczności, notacji z nawiasami kwadratowymi, czyli obiekt['metoda']().

Różnica w zachowaniu między whatIsThis a whatIsThisForArrow wynika z odmiennego traktowania słowa this przez te dwie funkcje. Funkcje zdefiniowane za pomocą składni funkcji zwykłej (takie jak whatIsThis) mają this dynamicznie powiązane z kontekstem, w którym są wywoływane. Funkcje strzałkowe (takie jak whatIsThisForArrow) dziedziczą this z otaczającego zakresu statycznego.

Destrukturyzujemy naszą metodę do oddzielnej zmiennej whatIsThis, a potem próbujemy ją wywołać. Jeśli nie spotkałeś się z destrukturyzacją, to zapraszam do tego artykułu. Jak można zauważyć, mimo że to ta sama funkcja, wartość zmiennej this uległa zmianie.

Istnieje pewna zasada, która w prosty sposób pozwala zrozumieć, czym będzie wartość słowa this. Zasada ta mówi, że w momencie, gdy dostęp do metody obiektu odbywa się za pomocą notacji kropkowej lub notacji z nawiasami kwadratowymi, ten obiekt staje się kontekstem w trakcie wywołania tej funkcji. W praktyce oznacza to, że this odnosi się do obiektu po lewej stronie kropki lub nawiasu kwadratowego podczas wywołania metody.

Co się dzieje, gdy nie stosujemy ani notacji kropkowej, ani notacji z nawiasami kwadratowymi?

Zasada ta ma zastosowanie także dla tego przypadku, ponieważ w momencie, gdy nic nie znajduje się po lewej stronie kropki (której de facto też nie ma), wartość this przyjmuje wartość undefined, co odzwierciedla brak określonej wartości.

Warto też zaznaczyć, że będzie to obiekt bezpośrednio na lewo od naszej funkcji.

Wywołanie za pomocą metod call i apply

W języku JavaScript istnieją dwie wbudowane metody: call i apply, które umożliwiają jawną zmianę kontekstu przy wywoływaniu funkcji. Pozwalają one na wywołanie funkcji z określoną przez nas wartością this. Istnieje również trzecia metoda - bind, która zwraca nową funkcję z trwale związaną wartością this (możemy również na stałe przypisać argumenty).

Metody takie jak call, apply, i bind nie wpływają na funkcje strzałkowe. Nawet po zastosowaniu tych metod, this w funkcji strzałkowej zachowuje swoją statyczną wartość.

Wywołanie z użyciem słowa new

Funkcja konstruktora jest podstawowym mechanizmem do tworzenia instancji obiektów w języku JavaScript. Służy ona do inicjalizowania właściwości nowo tworzonego obiektu. Zazwyczaj korzysta się z operatora this, aby odwołać się do właściwości obiektu i przypisać im odpowiednie wartości. Aby użyć funkcji jako konstruktor, musisz przed jej wywołaniem dodać słowo kluczowe new. Właśnie to słowo czyni wywoływanie funkcji jako konstruktor odrębnym punktem.

Gdy funkcja konstruktora jest wywołana ze słowem kluczowym new

  1. Tworzy nowy pusty obiekt JavaScript, nazywany newInstance.

  2. Łączy newInstance z prototypem funkcji konstruktora.

  3. Wywołuje funkcję konstruktora, przekazując jej argumenty. newInstance staje się this wewnątrz funkcji konstruktora.

  4. Jeśli funkcja konstruktora zwraca obiekt lub funkcję, to ta wartość staje się wynikiem wyrażenia new. W przeciwnym razie, jeśli funkcja konstruktora nie zwraca niczego lub zwraca wartość prymitywną, to wynikiem jest newInstance.

Ciekawostką jest, że nawet jeśli związaliśmy this za pomocą metody bind, to wywołanie funkcji za pomocą new nadal wpłynie na wartość this wewnątrz związanej funkcji.

Wywołanie pośrednie (callback)

Wywołanie pośrednie, znane także jako callback, odnosi się do sytuacji, w której funkcja jest przekazywana jako argument do innej funkcji i jest wywoływana w określonym czasie lub w określonych warunkach. Jest to ważny mechanizm w języku JavaScript, który pozwala na asynchroniczne operacje, obsługę zdarzeń oraz inne zaawansowane techniki programowania.

Wywołanie pośrednie może prowadzić do nieprzewidywalnego zachowania, gdyż sposób wywołania naszej funkcji callback zależy od szczegółów implementacji funkcji, do której callback został przekazany. Kontekst i argumenty, z jakimi funkcja callback zostanie wywołana, mogą się różnić w zależności od wewnętrznych mechanizmów tej funkcji.

W przypadku funkcji setTimeout w przeglądarkach internetowych, domyślnie this wewnątrz funkcji przekazanej jako callback jest powiązane z obiektem window. Jest to charakterystyczne zachowanie w środowisku przeglądarki, które może prowadzić do pewnych problemów, zwłaszcza jeśli oczekujemy, że this będzie wskazywać na inny obiekt.

Metoda addEventListener jest kolejnym przykładem zmiany kontekstu wywołania. Umożliwia ona dodawanie nasłuchiwania na konkretne zdarzenia na danym elemencie HTML. Ta metoda przyjmuje callback, który zostaje wywołany w odpowiednim momencie. W tym przypadku ⁣this jest automatycznie powiązane z elementem HTML, na którym nasłuchujemy zdarzenia.

Aby zapobiec domyślnemu powiązaniu this w funkcji callback, możemy użyć metody bind do jawnego określenia, na co this powinno wskazywać wewnątrz funkcji callback.

this w trybie ścisłym

W trybie nieścisłym (ang. non-strict mode) w języku JavaScript, jeśli funkcja jest wywoływana z wartością this, która nie jest obiektem, to wartość this zostaje zamieniona na obiekt.

W przypadku wartości null i undefined, są one zamieniane na obiekt globalThis, który jest obiektem globalnym (dla przeglądarek obiekt window) w danym środowisku wykonawczym JavaScript. Są to jedyne wartości prymitywne, które nie posiadają własnego konstruktora, więc nie są konwertowane na obiekty, używając go do tego celu.

W trybie ścisłym this może przyjąć wszelkie wartości, zarówno obiekty, jak i prymitywy, bez automatycznego przekształcania ich na wartość typu obiekt.

Podsumowanie

W tym artykule omówiliśmy kluczowe koncepcje związane z this w języku JavaScript. Zrozumieliśmy, że this odnosi się do kontekstu wykonania i może przyjmować różne wartości w zależności od kontekstu, w którym jest używane. Ujrzeliśmy różne przypadki, w których this może być używane oraz na co warto zwrócić uwagę.

Rozpoczęliśmy od omówienia funkcji strzałkowych, które nie tworzą własnego kontekstu this, lecz dziedziczą go z otaczającego zakresu. Wymieniliśmy ograniczenia funkcji strzałkowych, takie jak niemożliwość użycia ich jako konstruktorów oraz niemożność zmiany ich kontekstu this przy pomocy call, apply, czy bind.

Następnie, dokładnie przyjrzeliśmy się różnym scenariuszom wywoływania funkcji - od wywołania bezpośredniego, poprzez wywołanie jako metody obiektu, wywołanie z użyciem new, aż po wywołania pośrednie w formie callbacków. Wyjaśniliśmy, jak każdy z tych scenariuszy wpływa na wartość this, ukazując przy tym, że tryb ścisły zapewnia bardziej restrykcyjne i bezpieczne zachowanie this, eliminując jego domyślne zachowanie.

Mam nadzieję, że dzięki temu artykułowi this stało się dla Ciebie pojęciem mniej tajemniczym i że teraz, gdy staniesz przed pytaniem "Czym jest this w tej funkcji?", będziesz mógł odpowiedzieć z większą pewnością.

Avatar: Sylwester Sobczak

Junior Front-end Developer

Sylwester Sobczak

sylwester.sobczaak@gmail.com

Podziel się na

Dodaj komentarz

Komentarze (0)

Brak komentarzy