299 votes

Passer le bon contexte "this" au callback setTimeout ?

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

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

Quand j'essaye de faire ce qui précède, this fait référence à la fenêtre.

4 votes

Le double drapeau est-il vraiment valable ? Cette question a en fait été posée plus tôt.

1 votes

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

449voto

CMS Points 315406

EDIT : En résumé, en 2010, lorsque cette question a été posée, la manière la plus courante de résoudre ce problème était de sauvegarder une référence au contexte dans lequel l'élément 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);
} 

Dans la spécification ES5, qui vient d'être publiée un an avant cette date, elle a introduit l'élément bind méthode Dans la réponse originale, cela n'a pas été suggéré parce qu'il n'était pas encore largement supporté et qu'il fallait des polyfills pour l'utiliser, mais maintenant il est partout :

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

Le site bind crée une nouvelle fonction avec le nom this valeur pré-remplie.

Dans le JS moderne, c'est exactement le problème que les fonctions fléchées résolvent en ES6 :

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

Les fonctions fléchées n'ont pas de this de sa propre valeur, lorsque vous y accédez, vous accédez aux this de la portée lexicale qui l'entoure.

HTML5 également les minuteries normalisées en 2011, et vous pouvez maintenant passer des arguments à la fonction de rappel :

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

Voir aussi :

3 votes

Ça marche. J'ai testé le concept avec un jsbin script : jsbin.com/etise/7/edit

1 votes

Ce code implique la création d'une variable inutile (qui a une portée à l'échelle de la fonction) ; si vous avez correctement passé la variable this à la fonction, vous auriez résolu ce problème pour ce cas, pour map(), pour forEach(), etc., etc., en utilisant moins de code, moins de cycles CPU et moins de mémoire. ***Voir : La réponse de Misha Reyzlin.

237voto

Joel Purra Points 7366

Il existe des raccourcis prêts à l'emploi (sucre syntaxique) pour l'enveloppe de fonction avec laquelle @CMS a répondu. (Ci-dessous en supposant que le contexte que vous voulez est this.tip .)


ECMAScript 2015 ( tous les navigateurs courants et les smartphones , Node.js 5.0.0+)

Pour pratiquement tout le développement javascript (en 2020), vous pouvez utiliser fonctions de la flèche de la graisse qui sont partie de la spécification ECMAScript 2015 (Harmony/ES6/ES2015) .

Un site expression de la fonction flèche (également connu sous le nom de fonction de la flèche de la graisse ) a une syntaxe plus courte par rapport aux expressions de fonction et lie lexicalement l'élément this valeur [...].

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

Dans votre cas, essayez ceci :

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

ECMAScript 5 ( les navigateurs plus anciens et les smartphones , Node.js) et Prototype.js

Si vous ciblez navigateur compatible avec ECMA-262, 5e édition (ECMAScript 5) o Node.js ce qui (en 2020) signifie tous les navigateurs courants ainsi que les navigateurs plus anciens, vous pourriez utiliser Function.prototype.bind . Vous pouvez éventuellement passer n'importe quel argument de fonction pour créer fonctions partielles .

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

Encore une fois, dans votre cas, essayez ceci :

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

La même fonctionnalité a également été mis en œuvre dans Prototype (d'autres bibliothèques ?).

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


jQuery

Si vous utilisez déjà jQuery 1.4+, il existe une fonction prête à l'emploi qui permet de définir explicitement l'attribut this le contexte d'une fonction.

jQuery.proxy() : Prend une fonction et en retourne une nouvelle 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);
}

Underscore.js , lodash

Il est disponible dans Underscore.js, ainsi que dans lodash, sous forme de _.bind(...) 1 , 2

lier Lier une fonction à un objet, ce qui signifie qu'à chaque fois que la fonction est appelée, la valeur de l'objet de type this sera l'objet. En option, liez des arguments à la fonction pour les pré-remplir, également 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);
}

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

0 votes

Pourquoi pas par défaut func.bind(context...) ? Est-ce que je rate quelque chose ?

0 votes

Est-ce performant de créer constamment une nouvelle fonction (ce que fait bind) à chaque fois que vous appelez cette fonction ? J'ai un délai de recherche qui se réinitialise après chaque pression de touche, et il me semble que je devrais mettre en cache cette méthode "bound" quelque part pour la réutiliser.

0 votes

@Triynko : Je ne verrais pas la liaison d'une fonction comme une opération coûteuse, mais si vous appelez la même fonction liée plusieurs fois, vous pourriez aussi bien garder une référence : var boundFn = fn.bind(this); boundFn(); boundFn(); par exemple.

32voto

gryzzly Points 7144

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 ça :

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

Cette méthode est plus efficace en termes de performances qu'une recherche par portée (mise en cache). this dans une variable en dehors de l'expression du délai/de l'intervalle), puis en créant une fermeture (en utilisant la fonction $.proxy o Function.prototype.bind ).

Le code pour le faire fonctionner dans les IEs à partir de 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);
    }
});
@*/

1 votes

Lorsque vous créez une classe en utilisant la chaîne prototype et que vos méthodes sont des méthodes prototypes... 'bind' est la seule chose qui modifiera ce que 'this' est dans la méthode. En passant un paramètre au callback, vous ne modifiez pas ce que "this" est dans la fonction, donc une telle fonction prototype ne pourrait pas être écrite en utilisant "this" comme toute autre méthode prototype. Cela conduit à une incohérence. Bind est ce qui se rapproche le plus de ce que nous voulons réellement, et la fermeture pourrait être mise en cache dans "this" pour une meilleure performance de recherche et pour ne pas avoir à la créer plus d'une fois.

5voto

gumkins Points 591

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);

2voto

Sam Points 3315

Si vous utilisez underscore vous pouvez utiliser bind .

Par exemple

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X