Jeg har en konstruktionsfunktion, som registrerer en hændelseshåndtering:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', function () {
alert(this.data);
});
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
Men jeg kan ikke få adgang til data
-egenskaben for det oprettede objekt inden for callback'en. Det ser ud til, at this
ikke henviser til det objekt, der blev oprettet, men til et andet objekt.
Jeg har også forsøgt at bruge en objektmetode i stedet for en anonym funktion:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
men det udviser de samme problemer.
Hvordan kan jeg få adgang til det korrekte objekt?
this
this
(også kendt som "konteksten") er et særligt nøgleord i hver funktion, og dets værdi afhænger kun af hvordan funktionen blev kaldt, ikke hvordan/hvornår/hvor den blev defineret. Det er ikke påvirket af leksikale scopes som andre variabler (undtagen for pilefunktioner, se nedenfor). Her er nogle eksempler:
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
Hvis du vil vide mere om this
, kan du tage et kig på MDN-dokumentationen.
this
this
Du ønsker faktisk ikke at få adgang til this
i særdeleshed, men det objekt det refererer til. Derfor er en nem løsning blot at oprette en ny variabel, der også refererer til dette objekt. Variablen kan have et hvilket som helst navn, men almindelige navne er self
og that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Da self
er en normal variabel, overholder den reglerne for leksikalsk rækkevidde og er tilgængelig inde i callbacken. Dette har også den fordel, at du kan få adgang til værdien this
i selve callbacken.
this
i callback - del 1Det kan se ud som om, at du ikke har nogen kontrol over værdien af this
, fordi dens værdi sættes automatisk, men det er faktisk ikke tilfældet.
Hver funktion har metoden .bind
[docs], som returnerer en ny funktion med this
bundet til en værdi. Funktionen har nøjagtig samme opførsel som den, du kaldte .bind
på, blot er this
sat af dig. Uanset hvordan eller hvornår funktionen kaldes, vil this
altid henvise til den overgivne værdi.
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
I dette tilfælde binder vi callback'ens this
til værdien af MyConstructor
's this
.
Note: Når du binder kontekst for jQuery, skal du i stedet bruge jQuery.proxy
[docs]. Grunden til at gøre dette er, at du ikke behøver at gemme referencen til funktionen, når du afbinder en event callback. jQuery håndterer dette internt.
ECMAScript 6 introducerer pil-funktioner, som kan opfattes som lambda-funktioner. De har ikke deres egen this
-binding. I stedet bliver this
slået op i scope ligesom en normal variabel. Det betyder, at du ikke behøver at kalde .bind
. Det er ikke den eneste specielle adfærd de har, se MDN-dokumentationen for flere oplysninger.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
this
i callback - del 2Nogle funktioner/metoder, der accepterer callbacks, accepterer også en værdi, som callbackens this
skal henvise til. Dette er grundlæggende det samme som at binde det selv, men funktionen/metoden gør det for dig. Array#map
[docs] er en sådan metode. Dens signatur er:
array.map(callback[, thisArg])
Det første argument er callback og det andet argument er den værdi, som this
skal henvise til. Her er et konstrueret eksempel:
var arr = [1, 2, 3];
var obj = {multiplier: 42};
var new_arr = arr.map(function(v) {
return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument
Bemærkning: Hvorvidt du kan overdrage en værdi til this
eller ej, er normalt nævnt i dokumentationen for den pågældende funktion/metode. For eksempel beskriver jQuery's $.ajax
-metode [docs] en mulighed kaldet context
:
Dette objekt vil blive gjort til kontekst for alle Ajax-relaterede callbacks.
En anden almindelig manifestation af dette problem er, når en objektmetode bruges som callback/event handler. Funktioner er førsteklasses borgere i JavaScript, og udtrykket "metode" er blot en dagligdags betegnelse for en funktion, der er en værdi af en objektegenskab. Men denne funktion har ikke en specifik forbindelse til sit "indeholdende" objekt.
Overvej følgende eksempel:
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
Funktionen this.method
er tildelt som klikhåndtering, men hvis der klikkes på document.body
, vil den loggede værdi være undefined
, fordi this
i hændelseshåndteringen henviser til document.body
og ikke til instansen af Foo
.
Som allerede nævnt i begyndelsen afhænger det, hvad this
henviser til, af, hvordan funktionen kaldes, ikke hvordan den defineres.
Hvis koden var som følgende, ville det måske være mere tydeligt, at funktionen ikke har en implicit reference til objektet:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
Løsningen er den samme som nævnt ovenfor: Hvis tilgængelig, brug .bind
til eksplicit at binde this
til en specifik værdi
document.body.onclick = this.method.bind(this);
eller eksplicit kalde funktionen som en "metode" for objektet ved at bruge en anonym funktion som callback / event handler og tildele objektet (this
) til en anden variabel:
var self = this;
document.body.onclick = function() {
self.method();
};
eller bruge en pilefunktion:
document.body.onclick = () => this.method();
Det hele ligger i den "magiske" syntaks for at kalde en metode:
object.property();
Når du henter egenskaben fra objektet og kalder den på én gang, vil objektet være kontekst for metoden. Hvis du kalder den samme metode, men i separate trin, er konteksten i stedet det globale scope (vinduet):
var f = object.property;
f();
Når du får referencen til en metode, er den ikke længere knyttet til objektet, det er bare en reference til en almindelig funktion. Det samme sker, når du får referencen til brug som callback:
this.saveNextLevelData(this.setAll);
Det er der, hvor du binder konteksten til funktionen:
this.saveNextLevelData(this.setAll.bind(this));
Hvis du bruger jQuery, skal du bruge metoden $.proxy
i stedet, da bind
ikke understøttes i alle browsere:
this.saveNextLevelData($.proxy(this.setAll, this));
Udtrykket "context" bruges nogle gange til at henvise til det objekt, der refereres til af this. Brugen er uhensigtsmæssig, fordi den hverken semantisk eller teknisk passer til ECMAScript's this.
"Kontekst" betyder de omstændigheder, der omgiver noget, som giver mening, eller nogle forudgående og efterfølgende oplysninger, som giver ekstra mening. Udtrykket "kontekst" bruges i ECMAScript til at henvise til udførelseskontekst, som er alle parametre, anvendelsesområde og this inden for anvendelsesområdet for en eller anden eksekverende kode.
Dette er vist i ECMA-262 afsnit 10.4.2:
Sæt ThisBinding til den samme værdi som ThisBinding i den kaldende udførelseskontekst
hvilket klart angiver, at Dette er en del af en udførelseskontekst.
En eksekveringskontekst giver de omgivende oplysninger, der giver mening til den kode, der udføres. Den indeholder meget mere information end blot thisBinding.
Så værdien af this er ikke "kontekst", det er blot en del af en eksekveringskontekst. Det er i bund og grund en lokal variabel, der kan sættes af opkaldet til et hvilket som helst objekt og i strict mode til en hvilken som helst værdi overhovedet.