Menu

this w JavaScript: zasady wiązania i typowe pułapki

Jak naprawdę działa this w JavaScript: cztery zasady wiązania, czym różnią się funkcje strzałkowe i jak uniknąć klasycznej pułapki „this is undefined”.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

O this decyduje moment wywołania

Większość zamieszania wokół this bierze się z jednego błędnego założenia: że this zależy od tego, gdzie funkcję zdefiniowano. Nie zależy. this zależy od tego, jak funkcję wywołano.

Spójrz na tę samą funkcję wywołaną na dwa sposoby:

Ta sama funkcja, różne this. Pierwsze wywołanie ma user przed kropką, więc this to user. Drugie nie ma niczego przed kropką, więc this to undefined. Sama funkcja się nie zmieniła, zmieniło się miejsce wywołania.

Zapamiętaj tę myśl: żeby ustalić this, patrz na wywołanie, a nie na definicję.

Cztery zasady wiązania

this może zostać ustawione na cztery sposoby. Prawie każde pytanie o this rozwiążesz, ustalając, która zasada ma zastosowanie.

1. Wywołanie metody: obj.fn()

Gdy funkcję wywołujesz jako właściwość obiektu, this to ten obiekt:

To, co stoi przed kropką, staje się this. Tylko tyle.

2. Zwykłe wywołanie: fn()

Przy wywołaniu bez obiektu z przodu this to undefined w trybie ścisłym (który moduły i klasy włączają automatycznie) albo obiekt globalny w trybie nieścisłym:

Stąd biorą się błędy „this is undefined”. Oderwanie metody od obiektu zamienia wywołanie metody w zwykłe wywołanie:

Brak counter. przed wywołaniem oznacza brak wiązania. Funkcja nie pamięta obiektu, z którego pochodzi.

3. Jawne wiązanie: .call(), .apply(), .bind()

Możesz wymusić dowolne this:

.call i .apply od razu wywołują funkcję, a różnią się tylko sposobem przekazywania argumentów. .bind zwraca nową funkcję z na stałe zablokowanym this, co przydaje się przy callbackach.

4. Wywołanie z new: new Fn()

Wywołanie funkcji z new tworzy nowy obiekt i wiąże go z this:

Klasy używają tego mechanizmu pod spodem. Poznasz je w jednym z kolejnych rozdziałów.

Funkcje strzałkowe nie mają własnego this

Funkcje strzałkowe celowo łamią powyższe zasady. W ogóle nie wiążą this, tylko dziedziczą je z otaczającego zakresu, zamrożone w chwili definicji strzałki:

Strzałka na najwyższym poziomie modułu przechwytuje this modułu, czyli undefined. Choć wywołujesz ją jako user.arrow(), strzałka nie daje się ponownie związać.

Wygląda to na błąd, ale właśnie o to chodzi. Funkcje strzałkowe błyszczą wewnątrz metod, gdzie chcesz zachować zewnętrzne this:

Strzałka wewnątrz setInterval dziedziczy this z start, które wywołano jako timer.start(). Dlatego this.seconds działa. Zwykła function w tym miejscu dostałaby własne this (cokolwiek przekaże setInterval) i wszystko by się zepsuło.

Praktyczna zasada: funkcji strzałkowych używaj do callbacków wewnątrz metod, a zwykłych funkcji do samych metod.

Klasyczna pułapka z callbackiem

To najczęstszy sposób, w jaki this się rozjeżdża. Przekazujesz metodę jako callback, a ona gubi wiązanie:

setTimeout wywołuje funkcję zwykłym wywołaniem, a nie jako c.increment(). Są trzy sposoby, żeby to naprawić:

Wszystkie trzy działają. Opakowanie w strzałkę jest zwykle najczytelniejsze.

this na najwyższym poziomie

this na najwyższym poziomie zależy od tego, gdzie działa twój kod:

  • Skrypt w przeglądarce (nie moduł): this to window.
  • Moduł ES (w tym większość nowoczesnego kodu z bundlera): this to undefined.
  • Moduł CommonJS w Node.js: this to module.exports.

Do niezawodnego odwołania do obiektu globalnego w każdym środowisku używaj globalThis:

W praktyce nie warto polegać na this na najwyższym poziomie. Sięgaj po globalThis, gdy naprawdę potrzebujesz obiektu globalnego, a w pozostałych przypadkach przekazuj wartości jawnie.

Szybkie drzewo decyzyjne

Gdy widzisz this i nie wiesz, czym będzie, przejdź przez tę listę:

  1. Czy to funkcja strzałkowa? Wtedy this jest takie jak w otaczającym zakresie. Miejsce wywołania nie ma znaczenia.
  2. Czy wywołano ją z new? Wtedy this to nowy obiekt.
  3. Czy wywołano ją przez .call, .apply albo jako funkcję związaną? Wtedy this to przekazana wartość.
  4. Czy wywołano ją jako obj.method()? Wtedy this to obj.
  5. Czy wywołano ją jako zwykłe fn()? Wtedy this to undefined w trybie ścisłym.

Ta drabinka, w tej kolejności, rozstrzyga każdy przypadek.

Dalej: funkcje wyższego rzędu

Skoro this nie jest już zagadką, możesz przejść do wzorca, który czyni JavaScript tak ekspresyjnym: przekazywania funkcji jako wartości. Następnie przyjrzysz się funkcjom wyższego rzędu, czyli funkcjom przyjmującym lub zwracającym inne funkcje, oraz temu, jak napędzają metody tablic, obsługę zdarzeń i większość prawdziwego kodu JavaScript.

Najczęściej zadawane pytania

Na co wskazuje this w JavaScript?

this wskazuje na obiekt, na którym wywołano funkcję, a nie na miejsce, w którym ją zdefiniowano. W user.greet() this to user. W zwykłym wywołaniu greet() this to undefined w trybie ścisłym (albo obiekt globalny w trybie nieścisłym). Liczy się miejsce wywołania, a nie miejsce definicji.

Dlaczego this w mojej funkcji to undefined?

Najpewniej metoda została oderwana od obiektu i wywołana samodzielnie albo przekazana jako callback. const fn = user.greet; fn(); gubi wiązanie: w miejscu wywołania przed kropką nie ma żadnego obiektu. Napraw to przez .bind(user), opakowanie w funkcję strzałkową albo wywołanie w postaci user.greet().

Czym różni się this w funkcjach strzałkowych?

Funkcje strzałkowe nie mają własnego this. Dziedziczą je z otaczającego zakresu w chwili, gdy są definiowane. Dzięki temu świetnie nadają się na callbacki wewnątrz metod, gdzie chcesz zachować zewnętrzne this. Oznacza to też, że .call(), .apply() i .bind() nie zmienią this funkcji strzałkowej.

Czym jest this na najwyższym poziomie skryptu?

W zwykłym skrypcie w przeglądarce this na najwyższym poziomie to obiekt window. W module ES to undefined. W modułach CommonJS w Node.js to module.exports. Odniesieniem działającym w każdym środowisku jest globalThis, które zawsze wskazuje na obiekt globalny.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ