Javascript: Reference na mateřský objekt v event handlerech [příspěvek v archivu]
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).
Petr Staníček, 7. 3. 2008 v 12.17 • Rubrika: IT, Webdesign • Komentáře: 8
8 komentářů k článku »Javascript: Reference na mateřský objekt v event handlerech«
[1] Vložil(a): David Grudl 7. 3. 2008 v 14.38
WOW!!!
Škoda, že jsi tento článek nenapsal už před dvěma lety. Pixy, hodně toho svým čtenářům ještě dlužíš
[2] Vložil(a): ehmo 7. 3. 2008 v 15.08
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“
[3] Vložil(a): David Grudl 7. 3. 2008 v 15.38
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:
Celá magie se skrývá v této nenápadné funkci:
[4] Vložil(a): karf 7. 3. 2008 v 17.19
Já obvykle používám takovejhle vzor s anonymní funkcí:
[5] Vložil(a): Pixy 7. 3. 2008 v 17.31
[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í.[6] Vložil(a): karf 7. 3. 2008 v 19.14
[4] Je tam ten malý rozdíl, že v mém případě se vytváří closure pro anonymní funkci až v metodě load.
[7] Vložil(a): Pixy 7. 3. 2008 v 19.40
[5] Jasně. Já se jen bál, jestli jsem tam nepřehlídl nějaký zásadnější rozdíl.
Dík.
[8] Vložil(a): filer 19. 3. 2008 v 10.11
Akurát včera som narazil ešte na (formálne) trochu iné riešenie.
Váš komentář
Upozornění: Pokud vás téma tohoto příspěvku nezajímá, nebaví, dotýká se vás či vás dokonce uráží, tak prosím odejděte a pokud možno se nadále ve vlastním zájmu dalším podobným vyhýbejte. Hlavně se to prosím nesnažte autorovi sdělovat v komentářích, takové příspěvky nikoho nezajímají a budou nejspíš vymazány. Totéž platí pro vaše názory na osobu autora, na jiné přispěvatele, mluvení z cesty, ze spaní či pod vlivem omamných látek a další podobné výlevy nesouvisející s tématem článku. Jinými slovy, toto je prostor soukromého blogu určený pro komentování příspěvku publikovaného výše, nikoli k chatování a volné diskusi. Děkuji za pochopení.
Abyste mohli komentovat, musíte se přihlásit.