What does `this’ mean?

JavaScript to bardzo przyjazny język. Tak – to bardzo przyjazny język, kiedy już się wie, jak nietypowym językiem jest. Nie bez powodu uznawany jest za najbardziej niezrozumiany język świata. Przyczyn tego stanu jest wiele. W tym wpisie postaram się skupić na jednej z nich – operator `this’ i jego zmienność :).

Na początek

Jeśli w trakcie artykułu stwierdzisz, że warto byłoby uzupełnić wiedzę o JavaScript, aby dogłębniej zrozumieć treść polecam JavaScript na poważnie, szczególnie rozdziały 1.4.7 i 1.4.8.

Kontekst funkcji

Słowo kluczowe „this” wskazuje kontekst wywołania funkcji. Prosty przykład powinien wiele wyjaśnić:

[javascript]var obj = {
value : ‚hello world’,
talk : function()
{
alert(this.value);
}
};
obj.talk(); // wyświetli "hello world"[/javascript]

W metodzie `talk’ operator `this’ wskazywał na obiekt `obj’. Tak więc kontekstem wywołania metody `obj.talk’ (jeśli wywołamy ją w sposób pokazany na przykładzie) jest obiekt `obj’.

`this’ === `window’

Obiektem globalnym w każdym skrypcie uruchomionym w przeglądarce jest `window’. Tradycyjnie więcej powie kod:

[javascript]function example()
{
this.value = ‚hello world’;
}
window.example();
alert(value); // ‚hello world’
alert(window.value); // ‚hello world’
alert(window.value === value); // true
alert(this === window); // true[/javascript]

Zmienna `value’ oraz `window.value’ (to ta sama zmienna) mają wartość „hello world”, która została przypisana do zmiennej `this.value’ w metodzie `example’ wywołanej jako `window.example’…. No tak – a miało być prosto! 🙂

Spokojnie. Kilka prostych zasad i to wszystko będzie zrozumiałe :). Zasady te odnoszą się do zwykłego trybu. W trybie strict mode nie muszą one obowiązywać (większość skryptów – sądzę, że ponad 90% nie jest w trybie strict mode).

Zasady:

każda zmienna (funkcja to też zmienna [przechowująca referencję na funkcję]) w globalnej przestrzeni nazw może być wywołana na dwa sposoby:

[javascript]nazwa_zmiennej = xxx;[/javascript]

albo

[javascript]window.nazwa_zmiennej = xxx;[/javascript]

W funkcji, która przy wywołaniu nie zostanie powiązana z konkretnym kontekstem (inaczej mówiąc `this’ w tej funkcji nie przyjmie odpowiedniej wartości), operator `this’ posiada wartość `window’, czyli wskazuje na globalny kontekst. Stąd przypisanie w metodzie `example’ jakiegokolwiek pola do `this’ tak naprawdę przypisuje je do `window’.

Operator `new’

Operator `new’ tworzy instancję typu obiektu zdefiniowanego przez użytkownika lub jednego z wbudowanych typów obiektów, który posiada funkcję konstruktora (źródło: developer.mozilla.org).

Spróbujmy zatem uruchomić poprzedni kod z wykorzystaniem tego operatora:

[javascript]function Example()
{
this.value = ‚hello world’;
};
new Example();
alert(value); // ‚hello world’
alert(window.value); // ‚hello world’
alert(window.value === value); // true
alert(this === window); // true[/javascript]

W wyniku działania takiego kodu otrzymamy komunikat (w konsoli firebuga):

[text]ReferenceError: value is not defined[/text]

Jak sądzisz, dlaczego tak się stało?

Otóż wywołanie funkcji `Example’ (mogłoby być także `window.Example’) z wykorzystaniem operatora `new’ sprawia, że `this’ nie jest już w tej funkcji wiązane z `window’. Przez co przypisanie do niego pola `value’ nie dodaje zmiennej do globalnej przestrzeni nazw.

Dlaczego `Example’, a nie `example’?

Jest to konwencja proponowana przez D. Crockforda (m. in. autora książki Mocne strony JavaScript, czy formatu JSON). Jeśli metoda jest konstruktorem obiektu i do poprawnego działania wymaga użycia operatora `new’ jej nazwa powinna zaczynać się wielką literą.

Jak pokazał powyższy przykład operatory `this’ i `new’ potrafią być bardzo kłopotliwe. Stąd też istnieją głosy, aby pisać swoje skrypty w taki sposób by nie wymagały stosowania operatora `new’:

Ja od siebie zwykłem dodawać średnik za klamrą zamykającą konstruktor obiektu:

„Operator” self

Często zdarza się, że w kodzie będziemy mieli zagnieżdżone funkcje, np. tak:

[javascript]var obj = {
value : ‚hello world’,
talk : function()
{
// this === obj
setTimeout(function()
{
// this === window
 alert(this.value); // undefined!
}, 1000);
}
};[/javascript]

Zmienna `this.value’ zwróci wartość `undefined’, ponieważ zagnieżdżona funkcja posiada inny kontekst wywołania. Takich sytuacji w JS jest niezwykle wiele – szczególnie w przypadku wykorzystywania callbacków w Ajaksie.

Jednym z popularniejszych rozwiązań jest przypisanie wartości `this’ do zmiennej:

[javascript]var obj = {
value : ‚hello world’,
talk : function()
{
// this === obj
var self = this;
setTimeout(function()
{
// this === window
// self === obj
 alert(self.value); // ‚hello world’
}, 1000);
}
};[/javascript]

W powyższym przykładzie wykorzystałem mechanizm domknięć:

Warto pamiętać, że nie można usunąć, ani nadpisać `this’. Można natomiast przypisać jego wartość do innej zmiennej, a także można zmieniać poszczególne pola tego obiektu.

Wybrana nazwa dla „operatora” (`self’) może być oczywiście inna – jest to zwykła zmienna. Najczęściej jednak występują „self”, „that”, „_this”.

Ręczne ustawianie wartości `this’

To co za chwilę przeczytasz może zmienić Twoje życie. Pamiętaj, że nie będzie już odwrotu… Podczas wywoływania metody można określać w jakim kontekście ma zostać uruchomiona!

Metody `apply’ i `call’

Jak wcześniej wspomniałem można dowolnie sterować kontekstem wywołania metody w JavaScript. Tradycyjnie podeprę się przykładowym kodem:

[javascript]var x = {
value : ‚hello world’,
talk : function()
{
alert(‚jestem metodą x.talk i mówię: ‚ + this.value);
}
};

var y = {
value : ‚Good bye’,
talk : function()
{
alert(‚jestem metodą y.talk i mówię: ‚ + this.value);
}
};
x.talk(); // jestem metodą x.talk i mówię: hello world
y.talk(); // jestem metodą y.talk i mówię: good bye[/javascript]

Powyższy kod działa w pełni tak, jakbyśmy tego oczekiwali. Teraz jednak sprawię, aby metoda `x.talk’ mówiła „good bye”:

[javascript]// wcześniejszy kod
x.talk.apply(y); // jestem metodą x.talk i mówię: good bye[/javascript]

Cóż się tu stało? Wywołanie funkcji poprzez `nazwa_funkcji.apply(kontekst wywołania)’ powoduje, że wewnątrz tej funkcji `this’ zostaje związany z przekazanym obiektem.

Co więcej, nie musi być to nawet obiekt:

[javascript]function example()
{
alert(this + 3);
}
example.call(5); // 8
example.call(110); //113[/javascript]

Różnica między `apply’ i `call’

Metody `apply’ i `call’ działają tak samo. Jedyna różnica polega na przekazywanych parametrach:

[javascript]fun.apply(kontekst [, tablica argumentów])[/javascript]

 

[javascript]fun.call(kontekst [, argument1 [, argument2 [, … [, argumentN]]]])[/javascript]

Jeśli wywoływana metoda nie posiada żadnych parametrów – nie ma różnicy, której metody użyjesz. Jeśli posiada parametry należy wykorzystać tę metodę, która w danej sytuacji jest bardziej przydatna – jeśli masz akurat tablicę (lub array-like object) wykorzystaj `apply’, w przeciwnym wypadku `call’ wydaje się lepszym rozwiązaniem.

W najnowszych wersjach JavaScript pojawiała się dodatkowo metoda `bind’:

Zdarzenia

Jeszcze jednym ciekawym zagadnieniem jest zachowanie operatora `this’ w przypadku funkcji obsługi zdarzeń. TODO

Warto przeczytać:

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *