Question Comment accéder au bon 'ceci' 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 à data propriété de l'objet créé à l'intérieur du rappel. Ça ressemble à 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 d'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 à l'objet correct?


914
2017-11-29 06:13


origine


Réponses:


Ce que vous devriez savoir sur this

this (aka "le contexte") est un mot-clé spécial dans chaque fonction et sa valeur dépend uniquement de Comment 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 d'autres variables. 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`

En apprendre davantage sur this, jetez un oeil à la Documentation MDN.


Comment se référer à la bonne this

Ne pas utiliser this

Vous ne voulez pas accéder à this en particulier, mais l'objet auquel il se réfère. 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 communs sont self et that.

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

Depuis self est une variable normale, obéit à des règles d'étendue lexicales et est accessible dans le callback. Cela a également l'avantage que vous pouvez accéder à la this valeur du rappel lui-même.

Définir explicitement this du rappel - partie 1

Il pourrait sembler que vous n'avez aucun contrôle sur la valeur de this parce que sa valeur est définie automatiquement, mais ce n'est pas le cas.

Chaque fonction a la méthode .bind  [docs], qui retourne une nouvelle fonction avec this lié à une valeur. La fonction a exactement le même comportement que celui que vous avez appelé .bind sur, seulement ça this a été mis par vous. Peu importe comment ou quand cette fonction est appelée, this se référera toujours à 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 le rappel this à la valeur de MyConstructorde this.

Remarque: Lorsque le contexte de liaison pour jQuery, utilisez jQuery.proxy  [docs] au lieu. La raison pour cela est que vous n'avez pas besoin de stocker la référence à la fonction lors de la désactivation d'un rappel d'événement. jQuery gère cela en interne.

ECMAScript 6: Utilisation fonctions de flèche

ECMAScript 6 introduit fonctions de flèche, ce qui peut être considéré comme des fonctions lambda. Ils n'ont pas leur propre this contraignant. Au lieu, this est considéré comme une variable normale. Cela signifie que vous n'avez pas à 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));
}

Ensemble this du rappel - partie 2

Certaines fonctions / méthodes qui acceptent les callbacks acceptent également une valeur à laquelle le callback this devrait se référer à. C'est fondamentalement le même 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 second argument est la valeur this devrait 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 passer une valeur pour this est généralement mentionné dans la documentation de cette fonction / méthode. Par exemple, jQuery $.ajax méthode [docs] décrit une option appelée context:

Cet objet sera le contexte de tous les rappels liés à Ajax.


Problème courant: Utilisation de méthodes d'objet comme callbacks / gestionnaires d'événements

Une autre manifestation commune de ce problème est quand une méthode d'objet est utilisée en tant que gestionnaire de rappel / événement. Les fonctions sont des citoyens de première classe dans JavaScript et le terme «méthode» est simplement 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".

Considérez 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é en tant que gestionnaire d'événements de clic, mais si le document.body est cliqué, la valeur enregistrée sera undefined, parce que dans le gestionnaire d'événements, this se réfère à la document.body, pas l'instance de Foo.
Comme déjà mentionné au début, que this se réfère à dépend de la façon dont la fonction est appelé, pas comment c'est défini.
Si le code était comme suit, il pourrait être 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 le même que mentionné ci-dessus: Si disponible, utilisez .bind pour 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 en tant que 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 de flèche:

document.body.onclick = () => this.method();

1206
2017-11-29 06:13



Voici plusieurs façons d'accéder au contexte parent dans le contexte de l'enfant -

  1. Vous pouvez utiliser lier() fonction.
  2. Stockez la référence au contexte / ceci dans une autre variable (voir l'exemple ci-dessous).
  3. Utiliser ES6 Flèche les fonctions.
  4. Modifier le code / la conception de la fonction / l'architecture - pour cela, vous devriez avoir le contrôle sur modèles de conception en javascript.

1. Utilisation 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 Stocker 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 Fonction de flèche

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

139
2017-08-13 10:26



Tout est dans la syntaxe "magique" de l'appel d'une méthode:

object.property();

Lorsque vous obtenez la propriété de l'objet et l'appelez en une fois, l'objet sera le contexte de la méthode. Si vous appelez la même méthode, mais dans des étapes distinctes, le contexte est la portée globale (fenêtre) à la place:

var f = object.property;
f();

Quand 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 le $.proxy méthode à la place, comme bind n'est pas supporté dans tous les navigateurs:

this.saveNextLevelData($.proxy(this.setAll, this));

35
2018-05-21 00:11



Le problème avec "contexte"

Le terme "contexte" est parfois utilisé pour désigner l'objet référencé par ce. Son utilisation est inappropriée car elle ne convient ni sémantiquement ni techniquement ECMAScript ce.

"Le contexte" signifie les circonstances entourant quelque chose qui ajoute du sens, ou certaines informations précédentes et suivantes qui donnent un sens supplémentaire. Le terme "contexte" est utilisé dans ECMAScript pour désigner contexte d'exécution, qui est tous les paramètres, la portée et ce dans le cadre d'un code d'exécution.

Ceci est montré dans ECMA-262 section 10.4.2:

Définissez ThisBinding à la même valeur que ThisBinding du   appel contexte d'exécution

ce qui indique clairement que ce fait partie d'un contexte d'exécution.

Un contexte d'exécution fournit les informations environnantes qui ajoutent une signification au code en cours d'exécution. Il comprend beaucoup plus d'informations que le thisBinding.

Donc, la valeur de ce ce n'est pas un "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.


20
2018-06-01 00:44



D'abord, vous devez avoir une compréhension claire de scope et le comportement de 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 d'autres termes, la portée globale se réfère à l'objet window.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 dans une fonction.variable déclarée à l'intérieur d'une fonction n'est pas accessible normalement.this mot-clé dans la portée globale fait référence à l'objet window.this la fonction à l'intérieur fait également référence à l'objet de la fenêtre. this se référera 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 fonctions de rappel à l'intérieur:

Ici, j'ai une fonction constructeur appelée Personne. Il a 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 invoquez-le. Le callback a une tâche spécifique qui consiste à enregistrer la propriété name d'une instance de la fonction constructeur de 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)

}

Maintenant, créons une instance à partir du constructeur de la personne et invoquons différentes versions de sayNameVersionX (X se réfère à la méthode 1,2,3,4) avec niceCallback pour voir combien de façons nous pouvons manipuler le this rappel à l'intérieur de se référer à la person exemple.

var p1 = new Person('zami') // create an instance of Person constructor

lier : 

Qu'est-ce que faire 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 de la fonction de rappel.

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

premier lien this avec callback à l'intérieur de la méthode elle-même. Et pour la deuxième callback est passé avec l'objet lié à elle.

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

appel : 

le first argument du call méthode est utilisée comme this à l'intérieur de la fonction qui est invoquée avec callattaché à elle.

sayNameVersion3 les usages call pour manipuler le this faire référence à l'objet personne que nous avons créé, au lieu de l'objet window.

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

et on l'appelle comme suit:

p1.sayNameVersion3(niceCallback)

appliquer : 

Semblable à call, premier argument de apply se réfère à l'objet qui sera indiqué par this mot-clé.

sayNameVersion4 les usages apply manipuler this se référer à l'objet personne

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

et il s'appelle comme le suivant. Simplement le rappel est passé,

p1.sayNameVersion4(niceCallback)

15
2017-08-18 17:58



Nous ne pouvons pas lier cela à setTimeout(), comme il le fait toujours avec objet global (Fenêtre), si vous voulez accéder 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);

9
2017-11-17 14:32