keemor.com - Surfin' JavaScript Wave

Tu Haczyk IStnieje – ‚this’ cz. I

W językach programowania słowo kluczowe this wskazuje przeważnie w metodzie na obiekt, w którym jest metoda.
W związku z dynamicznym charakterem JS, fraza this przybiera wartości w zależności od kontekstu w jakim metoda jest wykonana i może być zarówno bardzo pomocne jak i przyprawić o nielichy ból głowy.
W pierwszej części „Tu Haczyk IStnieje” omówie jak zapanować nad this przy użyciu metody bind ulubionego frameworka.

Rozpatrzmy następujący obiekt Car1 z atrybutem color i metodą check zwracającą wartość color:


var Car1 = {
  color: "Red",
  check: function() {
    //console.info(this) - wyświetla wartość w konsoli Firebuga w Firefoxie
    alert( "Car color is " + this.color );
  }
};

Przypadek 1.

Wywołanie metody check działa tak jak byśmy się spodziewali zwracając Red.


// console.info -> Object color=Red
// alert -> Car color is Red
Car1.check();

Przypadek 2.

W drugim przypadku możemy być już trochę zaskoczeni, ale w metodzie checkthis będzie już wskazywać na Car2, a nie na Car1 i zwróci Blue.


var Car2 = {
  color: "Blue",
  check: Car1.check
};
//Object color=Blue
//Car color is Blue
Car2.check();

Przypadek 3.

Teraz wywołamy check w kontekście funkcji runFx, która jest metodą obiektu window. Konsekwencją jest to, że color jest undefined.


function runFx(f) {
	f();
}
//Window index.html
//Car color is undefined
runFx(Car1.check);

Przypadek 4.

Kolejny przypadek jest podobny do trzeciego jako, że funkcja setTimeout jest wywoływana w kontekście obiektu window, wartość color jest również undefined.


//Window index.html
//Car color is undefined
setTimeout( Car1.check, 100);

Przypadek 5.

W ostatnim przypadku zamiast funkcji setTimeout, użyłem metody delay i this przybrało wartość function().


//function()
//Car color is undefined
Car1.check.delay(0.1);

Rozwiązanie czyli bind.

Metoda bind powoduje, że wewnątrz funkcji na której ją wywołamy – this staje się referencją do obiektu, który podamy jako parametr metody bind.

W linii 6. stara funkcja check została zmienionana na _check.
Na koniec w linii 10. wywołujemy pseudo-konstruktor init, w którym this wskazuje na Car1 i dlatego, w linii 3. do check zbindowaliśmy na stałe obiekt Car1.


var Car1 = {
  init: function() {
  	this.check = this._check.bind(this);
  },
  color: "Red",
  _check: function() {
  	alert( "Car color is " + this.color );
  }
};
Car1.init();

Z tak zmienionym obiektem Car1, wszystkie poprzednie pięć przypadków daje ten sam rezultat.


//Object color=Red
//Car color is Red
Car1.check();
runFx(Car1.check);
Car2.check();
setTimeout( Car1.check, 100);
Car1.check.delay(0.1);

Ważny bonus do bind

Bind może być również użyte do konstruowania ostatecznej listy parametrów przekazanych do metody. W linii 3. dodajemy wartość initParam, a każdym wywołaniu wartość checkParam.


var Car1 = {
  init: function() {
  	this.check = this._check.bind(this, 'initParam');
  },
  color: "Red",
  _check: function() {
	alert($A(arguments).join(', '));  	
  }
};
Car1.init();

Linia 7. powyższego kodu zwraca initParam, checkParam czyli dwa parametry dla każdego wywołania poniżej.


//initParam, checkParam 
Car1.check('checkParam');
Car2.check('checkParam');
setTimeout( Car1.check, 500, 'checkParam' );
Car1.check.delay(0.5, 'checkParam');

Podsumowanie

Mam nadzieję, że to wyjaśnienie pomoże zrozumieć działanie this i korzyści jakie ze sobą.
W tej chwili trwają prace nad specyfikację ECMAScript 5 i metoda bind ma wejść do niej, więc za jakiś czas nie będzie zależeć od biblioteki JS, ale od przeglądarek, które będą wspierać ECMAScript 5.