javascript - Skicka rätt "this"-kontext till setTimeout-callback?


Hur för över jag kontext till setTimeout? Jag vill anropa this.tip.destroy() om this.options.destroyOnHide efter 1000 ms. Hur kan jag göra det?

if (this.options.destroyOnHide) {
     setTimeout(function() { this.tip.destroy() }, 1000);
} 

När jag provar ovanstående, refererar this till fönstret.

Svar #1

REDIGERA: Sammanfattningsvis, tillbaka 2010 när denna fråga ställdes var det vanligaste sättet att lösa detta problem att spara en referens till kontexten där setTimeout-funktionsanropet görs, eftersom setTimeout kör funktionen med this pekande på det globala objektet:

var that = this;
if (this.options.destroyOnHide) {
     setTimeout(function(){ that.tip.destroy() }, 1000);
} 

I ES5-specifikationen, som precis släpptes ett år innan den tiden, introducerade den bind-metoden, detta föreslogs inte i det ursprungliga svaret eftersom det ännu inte var allmänt stödd och man behövde polyfills för att använda den, men nu finns den överallt:

if (this.options.destroyOnHide) {
     setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}

Funktionen bind skapar en ny funktion med this-värdet förifyllt.

Nu i modern JS är detta exakt problemet som pilfunktioner löser i ES6:

if (this.options.destroyOnHide) {
     setTimeout(() => { this.tip.destroy() }, 1000);
}

Pilfunktioner har inget eget this-värde; när du får tillgång till det, får du tillgång till this-värdet i det omgivande lexikala sammanhanget.

HTML5 standardiserade också timers redan 2011, och du kan nu skicka argument till callback-funktionen:

if (this.options.destroyOnHide) {
     setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}

Se även:

  • setTimeout - Problemet med 'this'

Svar #2

Det finns färdiga genvägar (syntaktiskt socker) till funktionsomslagaren som @CMS svarade med. (Nedan antar vi att den kontext du vill ha är this.tip.)


ECMAScript 2015 (alla vanliga webbläsare och smartphones, Node.js 5.0.0+)

För praktiskt taget all JavaScript-utveckling (år 2020) kan du använda fat arrow-funktioner, som är en del av ECMAScript 2015 (Harmony/ES6/ES2015) specifikationen.

Ett pilfunktionsuttryck (även känt som fet pil-funktion) har en kortare syntax jämfört med funktionsuttryck och binder lexiskt värdet av this [...].

(param1, param2, ...rest) => { statements }

I ditt fall, prova detta:

if (this.options.destroyOnHide) {
    setTimeout(() => { this.tip.destroy(); }, 1000);
}

ECMAScript 5 (äldre webbläsare och smartphones, Node.js) och Prototype.js

Om du riktar dig mot en webbläsare som är kompatibel med ECMA-262, 5:e upplagan (ECMAScript 5) eller Node.js, vilket (2020) innebär alla vanliga webbläsare samt äldre webbläsare, kan du använda Function.prototype.bind. Du kan valfritt skicka med funktionens argument för att skapa partiella funktioner.

fun.bind(thisArg[, arg1[, arg2[, ...]]])

Återigen, i ditt fall, försök detta:

if (this.options.destroyOnHide) {
    setTimeout(this.tip.destroy.bind(this.tip), 1000);
}

Samma funktionalitet har också implementerats i Prototype (några andra bibliotek?).

Koden Function.prototype.bind kan implementeras så här om du vill ha anpassad bakåtkompatibilitet (men observera gärna noteringarna).


jQuery

Om du redan använder jQuery 1.4+, finns det en färdig funktion för att uttryckligen ange this-kontexten för en funktion.

jQuery.proxy(): Tar en funktion och returnerar en ny som alltid kommer att ha ett specifikt sammanhang.

$.proxy(function, context[, additionalArguments])

I ditt fall, prova detta:

if (this.options.destroyOnHide) {
    setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}

Underscore.js, lodash

Det finns tillgängligt i Underscore.js, såväl som lodash, som _.bind(...)1,2

bind Bind en funktion till ett objekt, vilket betyder att varje gång funktionen kallas kommer värdet av this att vara objektet. Valfritt, bind argument till funktionen för att förifylla dem, även känd som partiell applicering.

_.bind(function, object, [*arguments])

I ditt fall, prova detta:

if (this.options.destroyOnHide) {
    setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}

bind jquery underscore.js ecmascript-5 prototypejs node.js

Svar #3

I webbläsare andra än Internet Explorer kan du skicka parametrar till funktionen tillsammans efter fördröjningen:

var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

Så, du kan göra detta:

var timeoutID = window.setTimeout(function (self) {
  console.log(self); 
}, 500, this);

Detta är bättre när det gäller prestanda än ett scopesök (cachning av this i en variabel utanför timeout-/intervalluttrycket), och sedan skapa en closure (genom att använda $.proxy eller Function.prototype.bind).

Koden för att få det att fungera i IEs från Webreflection:

/*@cc_on
(function (modifierFn) {
  // you have to invoke it as `window`'s property so, `window.setTimeout`
  window.setTimeout = modifierFn(window.setTimeout);
  window.setInterval = modifierFn(window.setInterval);
})(function (originalTimerFn) {
    return function (callback, timeout){
      var args = [].slice.call(arguments, 2);
      return originalTimerFn(function () { 
        callback.apply(this, args) 
      }, timeout);
    }
});
@*/

Relaterade artiklar