Transmettre le contexte correct "ceci" à setTimeout callback? [dupliquer]

cette question a déjà une réponse ici:

Comment passer le contexte dans setTimeout ? Je veux appeler this.tip.destroy() si this.options.destroyOnHide après 1000 ms. Comment je peux faire ça?

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

quand j'essaie ce qui précède, this se réfère à la fenêtre.

185
demandé sur Nayuki 2010-01-25 07:44:21

5 réponses

vous devez sauvegarder une référence au contexte dans lequel l'appel de fonction setTimeout est effectué, car setTimeout exécute la fonction avec this pointant vers l'objet global:

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

vous pouvez facilement prouver que setTimeout mettre this à l'objet global par:

(function () {
  alert(this); // alerts hello
  setTimeout(function(){
    alert(this == window); // true
  }, 1000);
}).call("hello");

voir aussi:

259
répondu CMS 2016-07-07 20:15:54

il y a des raccourcis tout faits (sucre syntaxique) à la fonction wrapper @CMS auxquels on répond. (Ci-dessous en supposant que le contexte est this.tip .)


ECMAScript 5 ( les navigateurs actuels , Nœud.js) et le Prototype.js

si vous ciblez navigateur compatible avec ECMA-262, 5e édition (ECMAScript 5) ou noeud.js , vous pourriez utiliser Function.prototype.bind . Vous pouvez éventuellement passer tous les arguments de fonction pour créer partial functions .

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

encore, dans votre cas, essayez ceci:

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

la même fonctionnalité a également été implémentée dans le Prototype (d'autres bibliothèques?).

Function.prototype.bind peut être mis en œuvre comme ceci si vous voulez une rétrocompatibilité personnalisée (mais veuillez respecter les notes).


ECMAScript 2015 ( certains navigateurs , noeud.js 5.0.0+)

de pointe Pour le développement (2015), vous pouvez utiliser fat flèche fonctions , qui sont une partie de l'ECMAScript 2015 (Harmonie/ES6/ES2015) spécification ( exemples ).

An arrow function expression (également connu sous le nom fat arrow function ) a une syntaxe plus courte que les expressions de fonction et lie lexicalement la valeur this [...].

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

dans votre cas, essayez ceci:

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

jQuery

si vous utilisez déjà jQuery 1.4+, il y a une fonction prête à l'emploi pour définir explicitement le contexte this d'une fonction.

jQuery.proxy() : Prend une fonction et renvoie un nouveau qui aura toujours un contexte particulier.

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

dans votre cas, essayez ceci:

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

trait de Soulignement.js , lodash

c'est disponible en Underscore.js, ainsi que lodash, as _.bind(...) 1 , 2

bind lient une fonction à un objet, ce qui signifie que chaque fois que la fonction est appelée, la valeur de this sera objet. Optionnellement, liez les arguments à la fonction pour les pré-remplir, aussi connu sous le nom d'application partielle.

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

dans votre cas, essayez ceci:

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

204
répondu Joel Purra 2015-12-20 17:51:48

dans les navigateurs autres Qu'Internet Explorer, vous pouvez passer les paramètres à la fonction ensemble après le délai:

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

donc, vous pouvez faire ceci:

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

c'est mieux en termes de performance qu'une recherche de scope (mise en cache this dans une variable en dehors de l'expression timeout / interval), puis la création d'une fermeture (en utilisant $.proxy ou Function.prototype.bind ).

Le code pour le faire fonctionner dans IEs from 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);
    }
});
@*/
24
répondu Misha Reyzlin 2014-01-22 08:27:05

si vous utilisez underscore , vous pouvez utiliser bind .

E. G.

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}
2
répondu Sam 2017-10-20 18:13:08

NOTE: cela ne fonctionnera pas dans IE

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);
1
répondu gumkins 2014-05-27 10:32:59