Wednesday, April 27, 2011

Objekti meetodi kontekst JavaScriptis

Kuidas saada teada, millisele objektile viitab this vaikimisi kui tegu on objekti meetodiga? call, apply ja bind abil saab seda ka ise muuta, kuid mis on selleks vaikimisi?

Rusikareegel on, et tuleb vaadata funktsiooni käivitamise hetkel (st. sellel kohal, kus funktsiooniväärtuse taga on sulud) käivitatava väärtuse vasakut poolt - misiganes sealt vastu ei vaataks, see ongi kontekst.

Math.round();  // meetodi *round* kontekst on *Math*
window.Math.round(); // kontekst on *window.Math*

Juhul kui seal pole midagi, on kontekstiks window.

alert(); // funktsiooni *alert* kontekst on *window*

Kui tegu on callback funktsiooniga, siis on kõik sama - arvestada ei tule mitte selle seadmise hetke, vaid käivitamise hetke

function foo(callback){
    callback();  // <-- siin toimub käivitamine
}
foo(console.log); // <-- siin seadmine

Näites ei ole käivitatava meetodi log kontekstiks mitte console vaid window! Kuna konteksti seab käivitamise hetk ning lause callback(); juures, erinevalt lausest foo(console.log) enam konteksti määratud pole - vasakul pool kävitatavat väärtust pole märgitud midagi.

Et mitte jätta konteksti juhuse hooleks tasub tagasikutsefunktsioonide juures kasutada väärtuse seadmise hetkel meetodid bind (ES5 lisa, töötab vaid moodsaimates brauserites, samuti ka NodeJS platvormil) või käivitamise hetkel meetodeid call või apply.

// tagasikutsefunktsiooni kontekstiks saab *console*
foo(console.log.bind(console));

või

// käivitatava funktsiooniväärtuse kontekst on *console*
function foo(callback){
    callback.call(console);
}

Täpsustuseks märgiks veel ära, et kirjeldatud kontekst this olulisus seisneb selles, et kontekst viitab “iseendale”. See tähendab, et objekti meetodi sees saab kasutada objekti muid omadusi ja meetodeid, ilma et oleks vaja teada objekti nimetust vms viidet.

foo = {
    bar: 1,
    foobar: function(){
        alert(this.bar); // <-- kontekstiks on objekt *foo*
        // *this.bar* on sama mis *foo.bar*, kuid konteksti
        // tõttu pole vaja *foo* nime teada
    }
} 

NB! Kui tagasikutse funktsioonile on meetodiga bind kord juba kontekst seatud, siis seda call või apply abile muuta enam ei saa.

function bar(){
    alert(this.baz);
}
function foo(callback){
    callback.call({baz:1}); // <-- ei muuda enam midagi
}
foo(bar.bind({baz:2}); // <-- bind seab konteksti