Den richtigen "this" -Kontext an setTimeout callback übergeben?

249

Wie übergebe ich den Kontext setTimeout? Ich möchte anrufen, this.tip.destroy()wenn this.options.destroyOnHidenach 1000 ms. Wie kann ich das machen?

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

Wenn ich das oben genannte versuche, thisbezieht sich auf das Fenster.

JamesBrownIsDead
quelle
4
Ist das doppelte Flag wirklich gültig? Diese Frage wurde tatsächlich früher gestellt.
Sui Dream
1
if (this.options.destroyOnHide) {setTimeout (function () {this.tip.destroy ()} .bind (this), 1000); }
Zibri

Antworten:

352

BEARBEITEN: Zusammenfassend lässt sich sagen, dass 2010, als diese Frage gestellt wurde, der häufigste Weg zur Lösung dieses Problems darin bestand, einen Verweis auf den Kontext zu speichern, in dem der setTimeoutFunktionsaufruf ausgeführt wird, da setTimeoutdie Funktion mit dem thisVerweis auf das globale Objekt ausgeführt wird:

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

In der ES5-Spezifikation, die erst ein Jahr vor dieser Zeit veröffentlicht wurde, wurde die bindMethode eingeführt . Dies wurde in der ursprünglichen Antwort nicht vorgeschlagen, da sie noch nicht allgemein unterstützt wurde und Sie Polyfills benötigten, um sie zu verwenden, aber jetzt ist sie überall:

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

Die bindFunktion erstellt eine neue Funktion mit dem thisvorgefüllten Wert.

In der modernen JS ist dies genau das Problem, das Pfeilfunktionen in ES6 lösen :

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

Pfeilfunktionen haben keinen eigenen thisWert. Wenn Sie darauf zugreifen, greifen Sie auf den thisWert des umschließenden lexikalischen Bereichs zu.

HTML5 standardisierte auch Timer im Jahr 2011, und Sie können jetzt Argumente an die Rückruffunktion übergeben:

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

Siehe auch:

CMS
quelle
3
Es klappt. Ich habe das Konzept mit einem jsbin-Skript getestet: jsbin.com/etise/7/edit
John K
1
Bei diesem Code wird eine unnötige Variable erstellt (die einen funktionsweiten Gültigkeitsbereich hat). Wenn Sie thisdie Funktion korrekt übergeben haben, haben Sie dieses Problem für diesen Fall, für map (), für forEach () usw. usw. mit weniger Code, weniger CPU-Zyklen und weniger Speicher gelöst. *** Siehe: Misha Reyzlins Antwort.
HoldOffHunger
222

Es gibt vorgefertigte Verknüpfungen (syntaktischer Zucker) zum Funktions-Wrapper @CMS, mit denen geantwortet wird. (Unter der Annahme, dass der gewünschte Kontext ist this.tip.)


ECMAScript 5 ( aktuelle Browser , Node.js) und Prototype.js

Wenn Sie einen Browser verwenden, der mit ECMA-262, 5. Ausgabe (ECMAScript 5) oder Node.js kompatibel ist , können Sie diesen verwenden Function.prototype.bind. Sie können optional beliebige Funktionsargumente übergeben, um Teilfunktionen zu erstellen .

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

Versuchen Sie in Ihrem Fall erneut Folgendes:

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

Die gleiche Funktionalität wurde auch in Prototype implementiert (irgendwelche anderen Bibliotheken?).

Function.prototype.bindkann so implementiert werden, wenn Sie eine benutzerdefinierte Abwärtskompatibilität wünschen (beachten Sie jedoch die Hinweise).


ECMAScript 2015 ( einige Browser , Node.js 5.0.0+)

Für innovative Entwicklung (2015) können Sie mit Fett Pfeil Funktionen , die sind Teil der ECMAScript 2015 (Harmonie / ES6 / ES2015) Spezifikation ( Beispiele ).

Ein Pfeilfunktionsausdruck (auch als Fettpfeilfunktion bezeichnet ) hat im Vergleich zu Funktionsausdrücken eine kürzere Syntax und bindet den thisWert lexikalisch [...].

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

Versuchen Sie in Ihrem Fall Folgendes:

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

jQuery

Wenn Sie jQuery 1.4+ bereits verwenden, gibt es eine vorgefertigte Funktion zum expliziten Festlegen des thisKontexts einer Funktion.

jQuery.proxy () : Nimmt eine Funktion und gibt eine neue zurück, die immer einen bestimmten Kontext hat.

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

Versuchen Sie in Ihrem Fall Folgendes:

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

Underscore.js , lodash

Es ist in Underscore.js sowie in lodash als 1 , 2 verfügbar_.bind(...)

bind Bindet eine Funktion an ein Objekt. Dies bedeutet, dass bei jedem Aufruf der Funktion der Wert vonthisdas Objekt ist. Binden Sie optional Argumente an die Funktion, um sie vorab auszufüllen. Dies wird auch als Teilanwendung bezeichnet.

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

Versuchen Sie in Ihrem Fall Folgendes:

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

Joel Purra
quelle
Warum nicht standardmäßig func.bind(context...)? Vermisse ich etwas
aTei
Ist es performant, jedes Mal, wenn Sie dies aufrufen, eine neue Funktion zu erstellen (was die Bindung tut)? Ich habe ein Suchzeitlimit, das nach jedem Tastendruck zurückgesetzt wird, und es scheint nur so, als ob ich diese "gebundene" Methode irgendwo zur Wiederverwendung zwischenspeichern sollte.
Triynko
@Triynko: Ich würde das Binden einer Funktion nicht als teure Operation ansehen, aber wenn Sie dieselbe gebundene Funktion mehrmals aufrufen, können Sie auch eine Referenz behalten: var boundFn = fn.bind(this); boundFn(); boundFn();zum Beispiel.
Joel Purra
30

In anderen Browsern als Internet Explorer können Sie nach der Verzögerung gemeinsam Parameter an die Funktion übergeben:

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

Sie können dies also tun:

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

Dies ist in Bezug auf die Leistung besser als eine Scope-Suche (Zwischenspeichern thisin eine Variable außerhalb des Timeout- / Intervallausdrucks) und anschließendes Erstellen eines Abschlusses (mithilfe von $.proxyoderFunction.prototype.bind ).

Der Code, mit dem es in IEs von Webreflection funktioniert :

/*@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);
    }
});
@*/
Mischa Reyzlin
quelle
1
Wenn Sie eine Klasse mithilfe der Prototypenkette erstellen und Ihre Methoden Prototypmethoden sind ... "Binden" ist das einzige, was das "Dies" in der Methode ändert. Indem Sie einen Parameter an den Rückruf übergeben, ändern Sie nicht, was 'dies' in der Funktion ist, sodass eine solche Prototypfunktion nicht wie jede andere Prototypmethode mit 'dies' darin geschrieben werden kann. Das führt zu Inkonsistenzen. Binden ist das, was dem, was wir eigentlich wollen, am nächsten kommt, und der Abschluss könnte in "this" zwischengespeichert werden, um eine höhere Suchleistung zu erzielen und ihn nicht mehr als einmal erstellen zu müssen.
Triynko
3

HINWEIS: Dies funktioniert im IE nicht

var ob = {
    p: "ob.p"
}

var p = "window.p";

setTimeout(function(){
    console.log(this.p); // will print "window.p"
},1000); 

setTimeout(function(){
    console.log(this.p); // will print "ob.p"
}.bind(ob),1000);
Gummis
quelle
2

Wenn Sie verwenden underscore, können Sie verwendenbind .

Z.B

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