This i funkcje strzałkowe #68
Arrow function działają nieco inaczej niż zwykłe funkcje, jeśli chodzi o kontekst this
. Funkcje te nie mają własnego this
. To, że nie mają własnego this
nie oznacza, że nie możemy go używać w takiej funkcji. Jeżeli w funkcji strzałowej odwołamy się do this
to zostanie ono pożyczone od
funkcji nadrzędnej, jeżeli istnieje albo zostanie użyte domyślne this
z obiektu globalnego.
Przypominam też, że pracuję bez trybu ścisłego i uruchamiam kod w przeglądarce, więc obiektem globalnym jest window
nie używam też modułów ES6 gdzie zachowanie będzie inne.
Arrow function oraz this
Zobaczmy, jak to wszystko działa na przykładach:
const globalArrow = () => console.log(this);
globalArrow(); // Window
Pierwszy przykład to zwykła funkcja strzałkowa zdefiniowana w pliku, jako funkcja globalna. Tutaj działanie funkcji wygląda na takie samo jak w przypadku tradycyjnych funkcji JavaScript. Funkcja wywołana w kontekście globalnym zwraca jako this
obiekt window
.
Zdefiniujmy teraz funkcję, która będzie miała włączony tryb ścisły:
const globalArrowStrict = () => {
'use strict';
console.log(this);
};
globalArrowStrict(); // Window
Pamiętamy, że przy zwykłych funkcjach w trybie ścisłym, funkcja nie posiadała dostępu do obiektu globalnego przez this
. Wtedy this
wskazuje na undefined
. Tryb ścisły nie ma wpływu na arrow function, ponieważ ona sama nie ma w sobie this
, jest on zapożyczany z zakresu, który ją otacza, a jest nim zakres globalny.
Zobaczmy przykłady, kiedy funkcja strzałowa, znajduje się wewnątrz innej funkcji:
function normalStrict() {
'use strict';
const arrow = () => console.log(this);
arrow();
}
normalStrict(); // undefined
Deklaruję teraz normalną funkcję, która będzie pracowała w trybie ścisłym. W tej funkcji tworze także deklarację funkcji strzałkowej o nazwie arrow()
oraz wywołuję tę funkcję. Wypisuję również do konsoli kontekst this
prosto z funkcji strzałkowej.
Wcześniej mówiłem, że use strict
nie działa na funkcję strzałkową. Mówiłem też, że funkcje strzałkowe dziedziczą this
z kontekstu globalnego, albo z otaczającej jej funkcji. To jest właśnie ten drugi przypadek. Funkcja strzałkowa arrow
otrzymuje this
po funkcji normalStrict
, a ponieważ
jest tutaj włączony tryb ścisły, to this
jest undefined
. Jeżeli usuniemy deklarację use strict
to otrzymamy wartość window
.
Mówi się też, że funkcje strzałkowe dziedziczą this
z zakresu leksykalnego, a więc z tego miejsca gdzie zostały zadeklarowane. Natomiast tradycyjne funkcje, brały this
z kontekstu wywołania, a nie z miejsca deklaracji. Trzeba tylko zwrócić uwagę na istotną rzecz, jeżeli funkcja strzałowa jest
zadeklarowana w innej funkcji, dziedziczone jest this
z tej funkcji. Jeżeli nie, this
będzie ustawione na obiekt globalny lub undefined
gdy użyjemy modułów ES6.
Arrow function oraz this w obiektach
Arrow function nie powinny być używane jako metody dla obiektów:
const obj1 = {
a: 'boo',
b: () => {
console.log(this); // object window
console.log(this.a); // undefined
},
};
obj1.b();
Rozważmy taki przypadek, gdzie mamy obiekt z polem a
z wartością tekstową, oraz pole b
do którego dopisujemy arrow function. Mamy więc pod polem b
metodę dla tego obiektu, która wypisuje this
oraz wartość z pola a
za pomocą this.a
.
Wartości, jakie zostaną wypisane to window
oraz undefined
, ponieważ pole a
nie istnieje na obiekcie window
. Gdyby to była zwykła funkcja, wypisany byłby obiekt, oraz wartość boo
z pola a
.
Tak jak wspominałem, arrow function dziedziczy this
z innej otaczającej funkcji lub z zakresu globalnego, ponieważ w odróżnieniu od normalnych funkcji, arrow function nie mają swojego kontekstu this
. Kontekst ten nie wytworzy się, nawet jeżeli użyjemy jakiegoś obiektu jak w tym przypadku.
Funkcja strzałkowa nie ma po prostu własnego this
bierze go z miejsca, w którym została stworzona.
Kolejny przykład:
const a = {
b: {
c: {
boo: () => {
console.log(this); // Window
},
},
},
};
a.b.c.boo();
Stworzyłem obiekt, a
, który ma kolejno zagnieżdżone obiekty b
oraz c
. Dopiero na obiekcie c
wywołuję metodę strzałkową boo()
, która zwraca obiekt globalny window
. Nie ma znaczenia liczba zagnieżdżeń obiektów, obiekty same w sobie nie tworzą zakresu.
Tutaj znowu jest kontekst domyślny, a w tym przypadku jest to obiekt window
. Jeżeli użyjemy trybu ścisłego będzie to dalej obiekt window
, natomiast w modułach ES6 będzie to undefined
.
Arrow function deklarowane w klasie
W obiektach tworzonych literalnie lepiej nie używać metod jako arrow function. Zobaczmy, jak to działa w klasach:
class Arrow {
constructor() {
this.foo = () => console.log(this); // Arrow
}
boo = () => console.log(this); // Arrow
}
Przygotowałem klasę Arrow
, która w konstruktorze tworzy pole foo
do którego dopisuję arrow function. Również wykorzystuję na dzisiejszy dzień zupełną nowość i możliwość stworzenia nie pola z przypisaną funkcją strzałową, ale metodę strzałkową o nazwie boo()
. W roku 2020 jest to wciąż nowa
propozycja dla JavaScript, która oficjalnie nie jest jeszcze zaimplementowana, ale niektóre przeglądarki mogą ją już wspierać.
Gdy stworzymy obiekt z takiej klasy:
const arrow = new Arrow();
arrow.foo();
arrow.boo();
i wywołamy obie funkcje strzałkowe, zobaczymy, że mają prawidłowe odniesienie do this
. Wskazują na obiekt, który powstał.
Arrow {boo: ƒ, foo: ƒ}
boo: () => console.log(this)
foo: () => console.log(this)
Tworzenie obiektów za pomocą new
powoduje wywołanie konstruktora i stworzenie obiektu, który automatycznie pełni kontekst dla this
. Dlatego arrow functions jako metody w klasach są bezpieczne. Pamiętajmy, że klasy to też funkcje, więc funkcje strzałkowe jako metody mają ten this
, którego
oczekujemy.
Widzimy, że dla kontekstu this
powstaje coraz więcej zasad, a to jeszcze nie koniec.
Co warto zapamiętać:
-
funkcje strzałkowe nie mają własnego
this
muszą go dziedziczyć -
funkcje strzałkowe dziedziczą
this
z otaczającego je zakresu -
gdy funkcja strzałowa istnieje w innej funkcji, otrzymuje
this
od tej funkcji -
w pozostałych przypadkach
this
jest obiektem globalnym lubundefined
-
funkcje strzałkowe nie sprawdzają się jako metody w obiektach, ponieważ nie mają własnego
this
i nie mogą być wywołane z kontekstem obiektu, w którym istnieją -
funkcje strzałkowe są bezpieczne w obiektach stworzonych z klas
-
przykłady pokazane są bez trybu ścisłego oraz bez modułów ES6