Comment accéder au "this" correct dans un rappel?
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 en mesure d'accéder à la datapropriété de l'objet créé dans le rappel. Il semble thisne pas faire référence à 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 il présente les mêmes problèmes.
Comment puis-je accéder au bon objet?
Réponses
Ce que vous devez savoir this
this(aka "le contexte") est un mot-clé spécial à l'intérieur de chaque fonction et sa valeur ne dépend que de la façon dont la fonction a été appelée, pas comment / quand / où elle a été définie. Il n'est pas affecté par les portées lexicales comme les autres variables (sauf pour les fonctions fléchées, 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 this, consultez la documentation MDN .
Comment se référer à la bonne this
Utiliser les fonctions fléchées
ECMAScript 6 a introduit des fonctions fléchées , qui peuvent être considérées comme des fonctions lambda. Ils n'ont pas leur propre thisliaison. Au lieu de cela, thisest 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));
}
Ne pas utiliser this
En fait, vous ne voulez pas accéder thisen particulier, mais à l'objet auquel il fait référence . C'est pourquoi une solution simple consiste simplement à créer une nouvelle variable qui fait également référence à cet objet. La variable peut avoir n'importe quel nom, mais les plus courants sont selfet that.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Puisqu'il selfs'agit d'une variable normale, elle obéit aux règles de portée lexicale et est accessible à l'intérieur du callback. Cela présente également l'avantage que vous pouvez accéder à la thisvaleur du rappel lui-même.
Définition explicite thisdu rappel - partie 1
Il peut sembler que vous n'ayez aucun contrôle sur la valeur de thiscar sa valeur est définie automatiquement, mais ce n'est en fait pas le cas.
Chaque fonction a la méthode .bind [docs] , qui renvoie une nouvelle fonction thisliée à une valeur. La fonction a exactement le même comportement que celui que vous avez appelé .bind, seulement celui que thisvous avez défini. Peu importe comment ou quand cette fonction est appelée, thisfera toujours référence à la valeur transmise.
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 les rappels thisà la valeur de MyConstructor's this.
Remarque: Lors d'un contexte de liaison pour jQuery, utilisez plutôt jQuery.proxy [docs] . La raison pour cela est que vous n'ayez pas besoin de stocker la référence à la fonction lors de la dissociation d'un rappel d'événement. jQuery gère cela en interne.
Ensemble thisdu rappel - partie 2
Certaines fonctions / méthodes qui acceptent les rappels acceptent également une valeur à laquelle les rappels thisdoivent faire référence. C'est fondamentalement la même chose que le lier vous-même, mais la fonction / méthode le fait pour vous. Array#map [docs] est une telle méthode. Sa signature est:
array.map(callback[, thisArg])
Le premier argument est le rappel et le deuxième argument est la valeur à laquelle thisse référer. 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
Remarque: si vous pouvez ou non transmettre une valeur pour thisest généralement mentionné dans la documentation de cette fonction / méthode. Par exemple, la $.ajaxméthode de jQuery [docs] décrit une option appelée context:
Cet objet deviendra le contexte de tous les rappels liés à Ajax.
Problème courant: utilisation de méthodes d'objet comme rappels / gestionnaires d'événements
Une autre manifestation courante de ce problème est lorsqu'une méthode objet est utilisée comme gestionnaire de rappel / é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 vers 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.methodest affectée en tant que gestionnaire d'événements de clic, mais si l'utilisateur document.bodyclique sur, la valeur consignée sera undefined, car à l'intérieur du gestionnaire d'événements, se thisréfère au document.body, pas à l'instance de Foo.
Comme déjà mentionné au début, ce thisà quoi se réfère 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 pourrait être plus évident que la fonction n'a pas de 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 disponible, utilisez .bindpour se lier explicitement thisà une valeur spécifique
document.body.onclick = this.method.bind(this);
ou appelez explicitement la fonction en tant que "méthode" de l'objet, en utilisant une fonction anonyme comme gestionnaire de rappel / événement et affectez l'objet ( this) à une autre variable:
var self = this;
document.body.onclick = function() {
self.method();
};
ou utilisez une fonction fléchée:
document.body.onclick = () => this.method();
Voici plusieurs façons d'accéder au contexte parent dans le contexte enfant -
- Vous pouvez utiliser la
bind()fonction. - Stockez la référence au contexte / ceci dans une autre variable (voir l'exemple ci-dessous).
- Utilisez les fonctions ES6 Arrow .
- Modifier le code / la conception de la fonction / l'architecture - pour cela, vous devez maîtriser les modèles de conception en javascript.
1. Utiliser la bind()fonction
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', ( function () {
alert(this.data);
}).bind(this) );
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
Si vous utilisez underscore.js-http://underscorejs.org/#bind
transport.on('data', _.bind(function () {
alert(this.data);
}, this));
2 Stockez la référence au contexte / ceci dans une autre variable
function MyConstructor(data, transport) {
var self = this;
this.data = data;
transport.on('data', function() {
alert(self.data);
});
}
3 Flèche fonction
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
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 que vous 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 étapes séparées, le contexte est la portée globale (fenêtre) à la place:
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 fonction simple. La même chose se produit lorsque vous obtenez la référence à utiliser comme rappel:
this.saveNextLevelData(this.setAll);
C'est là que vous lieriez le contexte à la fonction:
this.saveNextLevelData(this.setAll.bind(this));
Si vous utilisez jQuery, vous devez utiliser la $.proxyméthode à la place, car elle bindn'est pas prise en charge dans tous les navigateurs:
this.saveNextLevelData($.proxy(this.setAll, this));
Le problème avec le "contexte"
Le terme «contexte» est parfois utilisé pour désigner l'objet référencé par celui-ci . Son utilisation est inappropriée parce qu'elle ne correspond pas non plus sémantiquement ou techniquement avec de ECMAScript ce .
«Contexte» signifie les circonstances entourant quelque chose qui ajoute du sens, ou certaines informations précédentes et suivantes qui donnent une signification supplémentaire. Le terme «contexte» est utilisé dans ECMAScript pour désigner le contexte d'exécution , qui est l'ensemble des paramètres, la portée, et ceci dans le cadre de certains codes d'exécution.
Ceci est illustré dans la section 10.4.2 de l'ECMA-262 :
Définissez ThisBinding sur la même valeur que ThisBinding du contexte d'exécution appelant
ce qui indique clairement que cela fait partie d'un contexte d'exécution.
Un contexte d'exécution fournit les informations environnantes qui ajoutent du sens au code en cours d'exécution. Il comprend beaucoup plus d'informations que simplement thisBinding .
Donc, la valeur de ce n'est pas "contexte", c'est juste une partie d'un contexte d'exécution. C'est essentiellement une variable locale qui peut être définie par l'appel à n'importe quel objet et en mode strict, à n'importe quelle valeur.
Vous devez connaître le mot clé "this".
Selon mon avis, vous pouvez implémenter "ceci" de trois manières (fonction Self / Arrow / Bind Method)
Ce mot-clé d'une fonction se comporte un peu différemment en JavaScript par rapport aux autres langages.
Il présente également quelques différences entre le mode strict et le mode non strict.
Dans la plupart des cas, la valeur de ceci est déterminée par la façon dont une fonction est appelée.
Il ne peut pas être défini par affectation pendant l'exécution, et il peut être différent à chaque fois que la fonction est appelée.
ES5 a introduit la méthode bind () pour définir la valeur de this d'une fonction indépendamment de la façon dont elle est appelée,
et ES2015 a introduit des fonctions fléchées qui ne fournissent pas leur propre liaison (il conserve cette valeur du contexte lexical englobant).
Méthode 1: Self - Self est utilisé pour maintenir une référence à l'original même lorsque le contexte change. C'est une technique souvent utilisée dans les gestionnaires d'événements (en particulier dans les fermetures).
Référence :https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function () {
alert(self.data);
});
}
Méthode 2 : Fonction de flèche - Une expression de fonction de flèche est une alternative syntaxiquement compacte à une expression de fonction régulière,
bien que sans ses propres liaisons avec les mots-clés this, arguments, super ou new.target.
Les expressions de fonction de flèche ne conviennent pas comme méthodes et ne peuvent pas être utilisées comme constructeurs.
Référence :https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
function MyConstructor(data, transport) {
this.data = data;
transport.on('data',()=> {
alert(this.data);
});
}
Méthode 3 : Bind - La méthode bind () crée une nouvelle fonction qui,
lorsqu'il est appelé, ce mot-clé est défini sur la valeur fournie,
avec une séquence donnée d'arguments précédant tout fourni lorsque la nouvelle fonction est appelée.
Référence: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
function MyConstructor(data, transport) {
this.data = data;
transport.on('data',(function() {
alert(this.data);
}).bind(this);
Tout d'abord, vous devez avoir une compréhension claire scopeet un comportement du thismot - clé dans le contexte de scope.
this& scope:
there are two types of scope in javascript. They are :
1) Global Scope
2) Function Scope
En bref, la portée globale fait référence à l'objet window. Les variables déclarées dans une portée globale sont accessibles de n'importe où. D'autre part, la portée de la fonction réside à l'intérieur d'une fonction. La variable déclarée à l'intérieur d'une fonction n'est normalement pas accessible depuis le monde extérieur. thisLe mot clé dans la portée globale fait référence à l'objet window. thisLa fonction inside fait également référence à l'objet window. Ainsi, thisnous référons toujours à la fenêtre jusqu'à ce que nous trouvions un moyen de manipuler thispour indiquer un contexte de notre choix.
--------------------------------------------------------------------------------
- -
- Global Scope -
- ( globally "this" refers to window object) -
- -
- function outer_function(callback){ -
- -
- // outer function scope -
- // inside outer function"this" keyword refers to window object - -
- callback() // "this" inside callback also refers window object -
- } -
- -
- function callback_function(){ -
- -
- // function to be passed as callback -
- -
- // here "THIS" refers to window object also -
- -
- } -
- -
- outer_function(callback_function) -
- // invoke with callback -
--------------------------------------------------------------------------------
Différentes façons de manipuler thisles fonctions de rappel à l'intérieur:
Ici, j'ai une fonction constructeur appelée Person. Il possède une propriété appelée nameet quatre méthode appelée sayNameVersion1, sayNameVersion2, sayNameVersion3, sayNameVersion4. Tous les quatre ont une tâche spécifique. Acceptez un rappel et appelez-le. Le rappel a une tâche spécifique qui consiste à consigner la propriété name d'une instance de la fonction de constructeur Person.
function Person(name){
this.name = name
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
this.sayNameVersion3 = function(callback){
callback.call(this)
}
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
}
function niceCallback(){
// function to be used as callback
var parentObject = this
console.log(parentObject)
}
Créons maintenant une instance à partir du constructeur de personne et invoquons différentes versions de la sayNameVersionXméthode (X se réfère à 1, 2, 3, 4) avec niceCallbackpour voir de combien de façons nous pouvons manipuler le thisrappel interne pour faire référence à l' personinstance.
var p1 = new Person('zami') // create an instance of Person constructor
lier :
Ce que bind fait est de créer une nouvelle fonction avec le thismot - clé défini sur la valeur fournie.
sayNameVersion1et sayNameVersion2utilisez bind pour manipuler thisla fonction de rappel.
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
le premier se lie thisavec un rappel à l'intérieur de la méthode elle-même, et pour le second, un rappel est passé avec l'objet qui lui est lié.
p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method
p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback
appeler :
Le first argumentde la callméthode est utilisé comme thisà l'intérieur de la fonction qui est invoquée avec qui lui est callattachée.
sayNameVersion3utilise callpour manipuler le thispour faire référence à l'objet personne que nous avons créé, au lieu de l'objet fenêtre.
this.sayNameVersion3 = function(callback){
callback.call(this)
}
et il est appelé comme suit:
p1.sayNameVersion3(niceCallback)
appliquer :
Similaire à call, le premier argument de applyfait référence à l'objet qui sera indiqué par thismot-clé.
sayNameVersion4utilise applypour manipuler thispour faire référence à un objet personne
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
et il est appelé comme suit. Simplement le rappel est passé,
p1.sayNameVersion4(niceCallback)
Nous ne pouvons pas lier cela à setTimeout(), car il s'exécute toujours avec un objet global (Window) , si vous voulez accéder au thiscontexte dans la fonction de rappel, puis en utilisant bind()la fonction de rappel, nous pouvons réaliser comme:
setTimeout(function(){
this.methodName();
}.bind(this), 2000);
La question tourne autour du thiscomportement des mots clés en javascript. thisse comporte différemment comme ci-dessous,
- La valeur de
thisest généralement déterminée par un contexte d'exécution de fonction. - Dans la portée globale,
thisfait référence à l'objet global (l'windowobjet). - Si le mode strict est activé pour une fonction, la valeur de
thisseraundefinedcomme en mode strict, objet global se réfère à laundefinedplace de l'windowobjet. - L'objet qui se trouve avant le point est ce à quoi ce mot-clé sera lié.
- Nous pouvons définir la valeur de cette explicitement
call(),bind()etapply() - Lorsque le
newmot-clé est utilisé (un constructeur), celui-ci est lié au nouvel objet en cours de création. - Les fonctions fléchées ne sont pas liées
this- à la place,thissont liées lexicalement (c'est-à-dire basées sur le contexte d'origine)
Comme la plupart des réponses le suggèrent, nous pouvons utiliser la fonction Flèche ou bind()Méthode ou Auto var. Je citerais un point sur les lambdas (fonction Flèche) du guide de style JavaScript de Google
Préférez utiliser les fonctions fléchées sur f.bind (this), et en particulier sur goog.bind (f, this). Évitez d'écrire const self = this. Les fonctions fléchées sont particulièrement utiles pour les rappels, qui transmettent parfois des arguments supplémentaires de manière inattendue.
Google recommande clairement d'utiliser des lambdas plutôt que de lier ou const self = this
La meilleure solution serait donc d'utiliser des lambdas comme ci-dessous,
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
Les références:
- https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
- flèches-fonctions-vs-lier
Actuellement, il existe une autre approche possible si les classes sont utilisées dans le code.
Avec la prise en charge des champs de classe, il est possible de procéder de la manière suivante:
class someView {
onSomeInputKeyUp = (event) => {
console.log(this); // this refers to correct value
// ....
someInitMethod() {
//...
someInput.addEventListener('input', this.onSomeInputKeyUp)
Bien sûr, sous le capot, toutes les bonnes fonctions fléchées relient le contexte, mais sous cette forme, cela semble beaucoup plus clair que la liaison explicite.
Comme il s'agit de la proposition de l'étape 3, vous aurez besoin de babel et du plugin babel approprié pour le traiter comme pour le moment (08/2018).
Une autre approche, qui est la manière standard depuis DOM2 de se lier thisdans l'écouteur d'événements, qui vous permet de toujours supprimer l'écouteur (entre autres avantages), est la handleEvent(evt)méthode de l' EventListenerinterface:
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
Des informations détaillées sur l'utilisation handleEventpeuvent être trouvées ici:https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
this dans JS:
La valeur de thisdans JS est déterminée à 100% par la manière dont une fonction est appelée et non par la manière dont elle est définie. Nous pouvons trouver relativement facilement la valeur de thispar la `` règle à gauche du point '' :
- Lorsque la fonction est créée à l'aide du mot-clé function, la valeur de
thisest l'objet à gauche du point de la fonction qui est appelée - S'il n'y a pas d'objet à gauche du point, la valeur de l'
thisintérieur d'une fonction est souvent l'objet global (globaldans le nœud,windowdans le navigateur). Je ne recommanderais pas d'utiliser lethismot - clé ici car il est moins explicite que d'utiliser quelque chose commewindow! - Il existe certaines constructions comme les fonctions fléchées et les fonctions créées à l'aide de la
Function.prototype.bind()fonction a qui peuvent fixer la valeur dethis. Ce sont des exceptions à la règle, mais elles sont vraiment utiles pour fixer la valeur dethis.
Exemple dans nodeJS
module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);
const obj1 = {
data: "obj1 data",
met1: function () {
console.log(this.data);
},
met2: () => {
console.log(this.data);
},
};
const obj2 = {
data: "obj2 data",
test1: function () {
console.log(this.data);
},
test2: function () {
console.log(this.data);
}.bind(obj1),
test3: obj1.met1,
test4: obj1.met2,
};
obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);
Production:
Laissez-moi vous guider à travers les sorties 1 par 1 (en ignorant le premier journal à partir du second):
thisest àobj2cause de la gauche de la règle de point, nous pouvons voir commenttest1est appeléobj2.test1();.obj2est à gauche du point et donc de lathisvaleur.- Même s'il
obj2reste du point, iltest2est liéobj1via labind()méthode. Donc, lathisvaleur estobj1. obj2est à gauche du point de la fonction qui est appelée:obj2.test3(). Par conséquent,obj2sera la valeur dethis.- Dans ce cas:
obj2.test4()obj2est à gauche du point. Cependant, les fonctions fléchées n'ont pas leur proprethisliaison. Par conséquent, il sera lié à lathisvaleur de la portée externe qui estmodule.exportsun objet qui a été enregistré au début. - Nous pouvons également spécifier la valeur de
thisen utilisant lacallfonction. Ici, nous pouvons passer lathisvaleur souhaitée comme argument, ce qui estobj2dans ce cas.
Je problème auquel est confronté avec Ngxgraphique en ligne xAxisTickFormattingfonction qui a été appelée à partir de HTML comme ceci: [xAxisTickFormatting]="xFormat". Je n'ai pas pu accéder à la variable de mon composant à partir de la fonction déclarée. Cette solution m'a aidé à résoudre le problème pour trouver le correct. J'espère que cela aidera le Ngxgraphique linéaire, les utilisateurs.
au lieu d'utiliser la fonction comme celle-ci:
xFormat (value): string {
return value.toString() + this.oneComponentVariable; //gives wrong result
}
Utilisez ceci:
xFormat = (value) => {
// console.log(this);
// now you have access to your component variables
return value + this.oneComponentVariable
}