This w obiektach

W tej części przyjrzymy się dokładnie jak this wygląda w obiektach JavaScript. Przyjrzymy się kilku przypadkom, które mogą nas zadziwić. Rozpatrzenie kilku przypadków pomoże nam zrozumieć wskaźnik this.

Na początek ważna uwaga, pracuję bez trybu ścisłego, czyli nie używam use strict, a także nie uruchamiam skryptu za pomocą żadnego bundlera, jak Parcel czy Webpack i nie używam modułów ES6. Wczytuję plik JavaScript za pomocą pliku HTML i uruchamiam w przeglądarce. Moim obiektem globalnym będzie zatem obiekt window. W innych konfiguracjach ten kod może działać inaczej.

This jako pole w obiekcie

Pierwszy przykład to zwykły obiekt, które zadeklarujemy w formie literalnej:

const obj1 = {
  a: 'some object',
  b: this,
  c: function() {
    return this;
  },
};
1
2
3
4
5
6
7

Mamy tutaj obiekt, który posiada trzy właściwości. Do pola b mam dopisaną mam wartość this. Natomiast do pola c mam dopisaną metodę, która zwraca this. Sprawdzimy teraz za pomocą conole.log czym jest this w tych dwóch różnych sytuacjach:

console.log(obj1.b); // window
console.log(obj1.c()); // {a: "some object", b: Window, c: ƒ}
1
2

To co zostało dopisane do pola b jest obiektem window. Okazuje się, więc, że this wskazuje na obiekt globalny. Pole b znajduje się w obiekcie obj1 i wywołanie nastąpiło przez obj1.b. Wcześniej mówiłem, że this jest tym co stoi przed kropką i jest to prawda, ale dotyczy to tylko wywołania metody. Kontekst this dotyczy tylko wywołania funkcji i metod, wtedy ustalana jest wartość dla this.

W tym przypadku pole b po prostu przetrzymuje wartość this, która jest wartością domyślną, czyli obiektem globalnym. Inny kontekst this tworzy się w momencie wywołania funkcji czy metody, a tutaj mamy zwykłe przypisanie this do pola b i odczytanie tej wartości przez console.log.

Wywołanie metody c daje już inny wynik. W tym przypadku this wskazuje na obiekt. Ponieważ jest to metoda i wywołanie następuje przez zadeklarowany obiekt, kontekstem dla this w tej metodzie staje się ten obiekt przed kropką. Następuje normalne wywołanie metody w jakimś kontekście, nie jak w przypadku pola b tylko przypisanie i odczytanie wartości.

This przy wywołaniu konstruktora

Przyjrzyjmy się przypadkowi tworzenia obiektów z wywołaniem konstruktora za pomocą funkcji, oraz za pomocą definicji klasy:

function Person() {
  this.a = 'foo';
}
1
2
3

Pierwszy przypadek to zwykła funkcja, która odwołuje się w sobie do this.a i przypisuje wartość foo. Jeżeli funkcję wywołamy z konstruktorem czyli za pomocą new Person() stworzymy obiekt:

const p = new Person();
console.log(p.a); // foo
1
2

Jeżeli wywołujemy new tworzony jest obiekt i automatycznie obiekt ten staje się kontekstem dla this w tej funkcji. Nie ma tutaj znaczenia gdzie wywołujemy new Person(). Użycie new sprawia, że obiekt, który powstaje z konstruktora tej funkcji staje się automatycznie kontekstem dla this. W przypadku wywoływania funkcji przez new nie musimy się martwić czym będzie kontekst.

Sprawdźmy jeszcze, co się stanie, jeżeli wywołamy funkcję bez new:

Person();
console.log(window.a); // foo
1
2

Funkcja została wywołana z kontekstem domyślnym, czyli na obiekcie window. W takim przypadku this w tej funkcji wskazuje na obiekt globalny window, albo w trybie ścisłym na undefined i wtedy zobaczycie błąd.

Takie wywołanie ostatecznie sprawiło, że przez this, pole a zostało dopisane do obiektu window. Ponieważ ta funkcja służy do tworzenia obiektów, nie powinniśmy raczej takich funkcji wywoływać bez użycia słowa kluczowego new.

This w klasie

Przed nami kolejny przypadek dotyczący klas, nie ma tutaj dużej różnicy między funkcją wywołaną z konstruktorem, a klasą.

Warto jednak przyjrzeć się tak zbudowanej klasie:

class MyClass {
  a = this;

  constructor() {
    this.b = this;
    this.c = 'foo';
  }
}

console.log(new MyClass());
1
2
3
4
5
6
7
8
9
10

Wykorzystałem tutaj nowy zapis tworzenia pola w klasie. Pole a zostaje po prostu zdefiniowane bez użycia metody constructor. Przypadek ten jest podobny do obiektu, który omawialiśmy na początku tego rozdziału. Tak zdefiniowane pole jest nowością w JavaScript i może nie działać w każdej przeglądarce czy w node.js. W roku 2020 jeszcze oficjalnie nie stanowi część języka JavaScript i możecie potrzebować skryptów polyfills.

Dodatkowo mamy też konstruktor, który definiuje pole b w standardowy sposób. Do obu pól dopisuję this. Tworzę obiekt i od razu wypisuję do konsoli:

MyClass {a: MyClass, b: MyClass, c: "foo"}
	a: MyClass {a: MyClass, b: MyClass, c: "foo"}
	b: MyClass {a: MyClass, b: MyClass, c: "foo"}
	c: "foo"
1
2
3
4

Widzimy, że zarówno pole a jak i b wskazuje na nowo stworzony obiekt. Działa to tak samo jak z funkcją i wywołaniem jej przez użycie new. Również to samo działanie jest przy klasach. Jeżeli tworzymy obiekt z klasy, zawsze musimy wywołać new i w ten sposób tworzony obiekt automatycznie staje się kontekstem this.

Może nam się wydawać, że pole a nie jest w żadnej metodzie i że jest to podobny przypadek do obiektu z polem b gdzie przypisaliśmy do niego this, przypadek ten rozważaliśmy na początku:

const obj1 = {
  a: 'some object',
  b: this,
  c: function() {
    return this;
  },
};
1
2
3
4
5
6
7

Przy klasach działa to jednak inaczej, ponieważ przy użyciu słowa kluczowego new następuje automatyczne dowiązanie do obiektu, który zostanie wytworzony przez konstruktor klasy. Dlatego w klasach, this ma zawsze kontekst wytworzonego z tej klasy obiektu. W głębi kodu JavaScript, klasy to funkcje, więc tak naprawdę przez słowo kluczowe new wywołujemy funkcje.

Używając czy to funkcji czy to klas ze słowem kluczowym new nie musimy się specjalnie martwić o kontekst this ponieważ będzie on zawsze obiektem, który powstaje z takiego wywołania. Nie ma tutaj znaczenia w jakim miejscu kodu występuje wywołanie.

Co warto zapamiętać

  • wiązanie this następuje tylko przy wywołaniu funkcji czy metody
  • domyślnie this wskazuje na obiekt globalny, w przeglądarce jest to window
  • gdy tworzymy obiekt za pomocą słowa kluczowego new , this będzie nowo stworzonym obiektem
  • gdy w stworzonym obiekcie literalnym stworzymy zwykłe pole i dopiszemy this, jest to kontekst domyślny czyli obiekt globalny