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 data
propriété de l'objet créé dans le rappel. Il semble this
ne 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 this
liaison. 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));
}
Ne pas utiliser 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 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 self
et that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Puisqu'il self
s'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 this
valeur du rappel lui-même.
Définition explicite this
du rappel - partie 1
Il peut sembler que vous n'ayez aucun contrôle sur la valeur de this
car 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 this
liée à une valeur. La fonction a exactement le même comportement que celui que vous avez appelé .bind
, seulement celui que this
vous avez défini. Peu importe comment ou quand cette fonction est appelée, this
fera 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 this
du rappel - partie 2
Certaines fonctions / méthodes qui acceptent les rappels acceptent également une valeur à laquelle les rappels this
doivent 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 this
se 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 this
est 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.method
est affectée en tant que gestionnaire d'événements de clic, mais si l'utilisateur document.body
clique sur, la valeur consignée sera undefined
, car à l'intérieur du gestionnaire d'événements, se this
ré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 .bind
pour 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 $.proxy
méthode à la place, car elle bind
n'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 scope
et un comportement du this
mot - 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. this
Le mot clé dans la portée globale fait référence à l'objet window. this
La fonction inside fait également référence à l'objet window. Ainsi, this
nous référons toujours à la fenêtre jusqu'à ce que nous trouvions un moyen de manipuler this
pour 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 this
les fonctions de rappel à l'intérieur:
Ici, j'ai une fonction constructeur appelée Person. Il possède une propriété appelée name
et 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 sayNameVersionX
méthode (X se réfère à 1, 2, 3, 4) avec niceCallback
pour voir de combien de façons nous pouvons manipuler le this
rappel interne pour faire référence à l' person
instance.
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 this
mot - clé défini sur la valeur fournie.
sayNameVersion1
et sayNameVersion2
utilisez bind pour manipuler this
la fonction de rappel.
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
le premier se lie this
avec 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 argument
de la call
méthode est utilisé comme this
à l'intérieur de la fonction qui est invoquée avec qui lui est call
attachée.
sayNameVersion3
utilise call
pour manipuler le this
pour 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 apply
fait référence à l'objet qui sera indiqué par this
mot-clé.
sayNameVersion4
utilise apply
pour manipuler this
pour 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 this
contexte 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 this
comportement des mots clés en javascript. this
se comporte différemment comme ci-dessous,
- La valeur de
this
est généralement déterminée par un contexte d'exécution de fonction. - Dans la portée globale,
this
fait référence à l'objet global (l'window
objet). - Si le mode strict est activé pour une fonction, la valeur de
this
seraundefined
comme en mode strict, objet global se réfère à laundefined
place de l'window
objet. - 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
new
mot-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,this
sont 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 this
dans l'écouteur d'événements, qui vous permet de toujours supprimer l'écouteur (entre autres avantages), est la handleEvent(evt)
méthode de l' EventListener
interface:
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
Des informations détaillées sur l'utilisation handleEvent
peuvent être trouvées ici:https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
this
dans JS:
La valeur de this
dans 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 this
par la `` règle à gauche du point '' :
- Lorsque la fonction est créée à l'aide du mot-clé function, la valeur de
this
est 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'
this
intérieur d'une fonction est souvent l'objet global (global
dans le nœud,window
dans le navigateur). Je ne recommanderais pas d'utiliser lethis
mot - 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):
this
est àobj2
cause de la gauche de la règle de point, nous pouvons voir commenttest1
est appeléobj2.test1();
.obj2
est à gauche du point et donc de lathis
valeur.- Même s'il
obj2
reste du point, iltest2
est liéobj1
via labind()
méthode. Donc, lathis
valeur estobj1
. obj2
est à gauche du point de la fonction qui est appelée:obj2.test3()
. Par conséquent,obj2
sera la valeur dethis
.- Dans ce cas:
obj2.test4()
obj2
est à gauche du point. Cependant, les fonctions fléchées n'ont pas leur proprethis
liaison. Par conséquent, il sera lié à lathis
valeur de la portée externe qui estmodule.exports
un objet qui a été enregistré au début. - Nous pouvons également spécifier la valeur de
this
en utilisant lacall
fonction. Ici, nous pouvons passer lathis
valeur souhaitée comme argument, ce qui estobj2
dans ce cas.
Je problème auquel est confronté avec Ngx
graphique en ligne xAxisTickFormatting
fonction 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 Ngx
graphique 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
}