J'ai une fonction constructeur qui enregistre un gestionnaire d'événements :
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);
Cependant, je ne suis pas capable d'accéder à la propriété data
de l'objet créé dans le callback. Il semble que this
ne se réfère pas à l'objet qui a été créé mais à un autre.
J'ai également essayé d'utiliser une méthode objet au lieu d'une fonction anonyme :
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
mais cela pose les mêmes problèmes.
Comment puis-je accéder au bon objet ?
[1] :
this
.this
(alias "le contexte") est un mot-clé spécial à l'intérieur de chaque fonction et sa valeur ne dépend que de comment la fonction a été appelée, et non de comment/quand/où elle a été définie. Elle n'est pas affectée par les portées lexicales comme les autres variables (sauf pour les fonctions flèches, voir ci-dessous). Voici quelques exemples :
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`
Pour en savoir plus sur this
, consultez la [documentation MDN] [this].
this
?this
.En fait, vous ne voulez pas accéder à this
en particulier, mais à l'objet auquel il fait référence. C'est pourquoi une solution facile est de créer simplement une nouvelle variable qui fait également référence à cet objet. Cette variable peut avoir n'importe quel nom, mais les plus courants sont self
et that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Comme self
est une variable normale, elle obéit aux règles de portée lexicale et est accessible à l'intérieur du callback. Ceci a aussi l'avantage que vous pouvez accéder à la valeur this
de la callback elle-même.
this
de la callback - partie 1On pourrait croire que vous n'avez aucun contrôle sur la valeur de this
parce que sa valeur est fixée automatiquement, mais ce n'est en fait pas le cas.
Chaque fonction possède la méthode [.bind
[docs]][bind], qui renvoie une nouvelle fonction avec this
lié à une valeur. La fonction a exactement le même comportement que celle sur laquelle vous avez appelé .bind
, sauf que this
a été défini par vous. Peu importe comment et quand cette fonction est appelée, this
fera toujours référence à la valeur passée.
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);
}
Dans ce cas, nous lions le this
de la callback à la valeur du this
de MyConstructor
.
Note: Lorsque vous liez un contexte pour jQuery, utilisez plutôt [jQuery.proxy
[docs]][proxy]. La raison de faire cela est que vous n'avez pas besoin de stocker la référence à la fonction lors du débridage d'un appel d'événement. jQuery gère cela en interne.
ECMAScript 6 introduit les fonctions flèches, qui peuvent être considérées comme des fonctions lambda. Elles n'ont pas leur propre liaison this
. Au lieu de cela, this
est recherché dans la portée comme une variable normale. Cela signifie que vous n'avez pas besoin d'appeler .bind
. Ce n'est pas le seul comportement spécial qu'ils ont, veuillez vous référer à la documentation MDN pour plus d'informations.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
this
de la callback - partie 2Certaines fonctions/méthodes qui acceptent les callbacks acceptent également une valeur à laquelle le this
du callback doit faire référence. C'est en fait la même chose que de le faire soi-même, mais la fonction/méthode le fait pour vous. [Array#map
[docs]][map] est une telle méthode. Sa signature est la suivante :
array.map(callback[, thisArg])
Le premier argument est le callback et le deuxième argument est la valeur à laquelle this
doit faire référence. Voici un exemple artificiel :
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
Note: La possibilité de passer ou non une valeur pour this
est généralement mentionnée dans la documentation de cette fonction/méthode. Par exemple, [la méthode $.ajax
de jQuery [docs]][ajax] décrit une option appelée context
:
Cet objet deviendra le contexte de tous les callbacks liés à Ajax.
Une autre manifestation courante de ce problème est l'utilisation d'une méthode objet comme callback/gestionnaire d'événement. Les fonctions sont des citoyens de première classe en JavaScript et le terme "méthode" est juste un terme familier pour une fonction qui est une valeur d'une propriété d'objet. Mais cette fonction n'a pas de lien spécifique avec son objet "contenant".
Prenons l'exemple suivant :
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
La fonction this.method
est assignée comme gestionnaire d'événement de clic, mais si le document.body
est cliqué, la valeur enregistrée sera undefined
, parce qu'à l'intérieur du gestionnaire d'événement, this
fait référence au document.body
, et non à l'instance de Foo
.
Comme nous l'avons déjà mentionné au début, ce à quoi this
fait référence dépend de la façon dont la fonction est appelée, et non de la façon dont elle est définie.
Si le code était comme le suivant, il serait plus évident que la fonction n'a pas une référence implicite à l'objet :
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
La solution est la même que celle mentionnée ci-dessus : Si possible, utilisez .bind
pour lier explicitement this
à une valeur spécifique.
document.body.onclick = this.method.bind(this);
ou appelez explicitement la fonction comme une "méthode" de l'objet, en utilisant une fonction anonyme comme callback / gestionnaire d'événement et assignez l'objet (this
) à une autre variable :
var self = this;
document.body.onclick = function() {
self.method();
};
ou utiliser une fonction flèche :
document.body.onclick = () => this.method();
[proxy] : http://api.jquery.com/jQuery.proxy/ [ceci] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this [arrow] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions [bind] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind [map] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map [ajax] : http://api.jquery.com/jQuery.ajax/
Tout est dans la syntaxe "magique" de l'appel d'une méthode :
object.property();
Lorsque vous récupérez la propriété de l'objet et l'appelez en une seule fois, l'objet sera le contexte de la méthode. Si vous appelez la même méthode, mais en plusieurs étapes, le contexte est plutôt la portée globale (fenêtre) :
var f = object.property;
f();
Lorsque vous obtenez la référence d'une méthode, elle n'est plus attachée à l'objet, c'est juste une référence à une simple fonction. Il en va de même lorsque vous obtenez la référence à utiliser comme callback :
this.saveNextLevelData(this.setAll);
C'est là que vous liez le contexte à la fonction :
this.saveNextLevelData(this.setAll.bind(this));
Si vous utilisez jQuery, vous devez utiliser la méthode $.proxy
à la place, car bind
n'est pas supporté par tous les navigateurs :
this.saveNextLevelData($.proxy(this.setAll, this));
Le terme "context" est parfois utilisé pour désigner l'objet référencé par this. Son utilisation est inappropriée car il ne correspond ni sémantiquement ni techniquement à l'objet this de l'ECMAScript[1].
[Le terme "contexte"][2] désigne les circonstances entourant quelque chose qui ajoutent du sens, ou certaines informations précédentes et suivantes qui donnent un sens supplémentaire. Le terme "contexte" est utilisé dans l'ECMAScript pour désigner le [contexte d'exécution][3], c'est-à-dire l'ensemble des paramètres, de la portée et de ceci dans la portée d'un code en cours d'exécution.
Ceci est illustré dans [ECMA-262 section 10.4.2][4] :
Définir le ThisBinding à la même valeur que le ThisBinding du > contexte d'exécution appelant. contexte d'exécution appelant
ce qui indique clairement que this fait partie d'un contexte d'exécution.
Un contexte d'exécution fournit les informations environnantes qui donnent un sens au code en cours d'exécution. Il comprend beaucoup plus d'informations que le simple [thisBinding][5].
Ainsi, la valeur de this n'est pas un "contexte", c'est juste une partie d'un contexte d'exécution. Il s'agit essentiellement d'une variable locale qui peut être définie par l'appel à n'importe quel objet et, en mode strict, à n'importe quelle valeur.
[1] : http://ecma-international.org/ecma-262/5.1/#sec-10.3 [2] : http://www.oxforddictionaries.com/definition/english/context [3] : http://ecma-international.org/ecma-262/5.1/#sec-10.3 [4] : http://ecma-international.org/ecma-262/5.1/#sec-10.4.2 [5] : http://ecma-international.org/ecma-262/5.1/#sec-11.1.1