V Javascriptu se mi často stává, že mám vytvořenou nějakou objektovou
strukturu a nějakou metodu takového objektu chci použít jako ovladač
události (event handler). Ale zde nastává problém v tom, že
klíčové slovo this odpovídá při volání handleru ne
mateřskému objektu, kterému patří metoda, ale objektu, který vyvolal
událost. Příklad:
function myObject(data) {
this.Data = data;
this.clickHandler = function() {
alert(this);
}
// ...
}
var obj = new myObject(data);
someElement.onclick = obj.clickHandler;
Pokud nastane událost someElement.onclick, zavolá se sice
„metoda“ objektu obj.clickHandler, ale this bude
v tom okamžiku znamenat objekt someElement, na které událost
vznikla.
Často je v těchto handlerech potřeba pracovat s mateřským objektem – ostatně většinou takovéto konstrukce píšeme coby nějaké obecné knihovny či komplexní struktury a chceme využívat hlavní výhody objektového programování: tedy uzavřenost a kontext, kdy si každý podobjekt spravuje své věci, nemíchá se do jiných činností a nikdo se zase nemíchá do té jeho. Tento nedostatek a chybějící zpětná reference na mateřský objekt se dá různými způsoby obejít. Nejčastěji doplněním reference do potenciálních event triggerů – tedy objektů, které můžou vyvolat danou událost a přes které se pak lze na mateřský objekt dostat, např.:
// ...
this.clickHandler = function() {
alert(this.owner);
}
// ...
someElement.onclick = obj.clickHandler;
someElement.owner = obj;
V tomto případě bude při vyvolání události v handleru hodnotou
this.owner právě mateřský objekt obj. Jenomže ne
vždycky je možné takové přiřazení udělat – objektů je vytvořených
mnoho a v daném okamžiku nevíme, se kterým se právě pracuje, nebo
event trigger není dostupný, abychom do něj mohli něco doplnit atd.
Typicky se to stává při použití externích knihoven a frameworků. Vezměme
jako příklad třeba jQuery a načítání dat přes
ajax:
function myObject() {
this.load = function() {
$.get( this.URL, this.Params, this.onloadHandler );
}
this.onloadHandler = function(data) {
/* this.Data = data ??? */
}
// ...
}
var obj = [];
for (var i=0;i<objCnt;i++) obj[i] = new myObject(data);
// ...
obj[x].load();
Zde je po úspěšném načtení ajaxových dat opět coby handler
zavolána metoda obj.onloadHandler, ale tentokrát v ní
this odpovídá instanci interního objektu
jQuery.ajax, ke kterému se (slušně) nedostaneme a nedá se do
něj nic přidávat. Instancí MyObject je také mnoho a nemůžeme
zjistit, který to právě je. A konečně volání ajaxu je asynchronní a
může jich probíhat současně několik, takže nějaká globální proměnná
nás taky nezachrání.
Je zde ale jedna věc, která není na první pohled vůbec zřejmá: ačkoli je dotyčný handler volán cizím objektem zvnějšku a je jakoby „vytržen“ ze svého kontextu v mateřském objektu, pořád zůstává jeho „metodou“ a při volání má jeho kontext a jmenný prostor. Jsou zde tedy dostupné všechny proměnné definované lokálně v rámci mateřského objektu. A dá se toho využít:
function myObject() {
var thisObj = this;
this.load = function() {
$.get( this.URL, this.Params, this.onloadHandler );
}
this.onloadHandler = function(data) {
thisObj.Data = data;
thisObj.doAnythingElse();
}
// ...
}
V lokální proměnné thisObj má objekt uloženu referenci
sám na sebe, a tato reference bude dostupná i event handleru
zavolanému úplně jiným objektem. Bude zde platit, že this je
objekt, který vygeneroval událost (event trigger), a
thisObj je objekt sám.
Doplnění:
Díky Davidovi za skvělý nápad v komentářích! Ještě jsem ho trochu upravil a vzniklo tak zatím nejlepší a formálně asi nejčistší řešení:
function dynamicHandler(obj,method) {
return function(){ method.apply(obj,arguments) };
}
// ...
function myObject() {
// ...
this.load = function() {
$.ajax({
url: this.URL,
data: this.Params,
success: dynamicHandler(this,this.onloadHandler),
error: dynamicHandler(this,this.onerrorHandler)
});
}
this.onloadHandler = function(Data) {
// ...
}
this.onerrorHandler = function(XHR,ErrorString,Exception) {
// ...
}
}
Jak je asi vidět, takhle to řešení funguje i s libovolným počtem
parametrů, což je docela důležité. Standardní event handlery sice
předávají obvykle parametr jen jeden (event), ale např. jQuery už
vrací parametrů více (třeba callback $.ajax.error vrací
argumenty až tři).
WOW!!!
Škoda, že jsi tento článek nenapsal už před dvěma lety. Pixy, hodně toho svým čtenářům ještě dlužíš
velmi pekny clanok.
ono javascript je divny uz vo svojej podstate
alert(typeof Boolean[-6]);
clovek obcas zasne ak featury ma v sebe a co vsetko dokaze „vygrcat“
Abych vysvětlil své nadšení, s tímto problémem jsem se taky hodně trápil a nakonec jsem našel řešení, které používám třeba v Míchátku href=„http://latrine.dgx.cz/color-mixer-aneb-michatko“ rel=„nofollow ugc“>http://latrine.dgx.cz/color-mixer-aneb-michatko:
Celá magie se skrývá v této nenápadné funkci:
Já obvykle používám takovejhle vzor s anonymní funkcí:
[3] Je tam nějaký jiný rozdíl, než že místo
thisObjse to jmenujethat? Z hlediska principu je jedno, jestli je tam anonymní funkce nebo je mezi tím více volání. Nicméně díky za doplnění.[4] Je tam ten malý rozdíl, že v mém případě se vytváří closure pro anonymní funkci až v metodě load.
[5] Jasně. Já se jen bál, jestli jsem tam nepřehlídl nějaký zásadnější rozdíl.
Dík.
Akurát včera som narazil ešte na (formálne) trochu iné riešenie.