javascript - une - Comment accéder au bon 'ceci' dans un rappel?




selecteur javascript (4)

Ce que vous devriez savoir à this sujet

this (aka "le contexte") est un mot-clé spécial dans chaque fonction et sa valeur dépend seulement 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 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`

Pour en savoir plus à this sujet, consultez la documentation MDN .

Comment se référer à la correcte

Ne l'utilise pas

En fait, 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 courants sont self et that .

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

Puisque self est une variable normale, elle obéit à des règles d'étendue lexicales et est accessible à l'intérieur du callback. Cela a également l'avantage que vous pouvez accéder à this valeur de la callback elle-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 valeur car sa valeur est définie automatiquement, mais ce n'est pas le cas.

Chaque fonction a la méthode .bind [docs] , qui renvoie une nouvelle fonction avec this limite à une valeur. La fonction a exactement le même comportement que celui que vous avez appelé .bind on, seulement que this été défini 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 MyConstructor le callback à la valeur de MyConstructor .

Remarque: Lorsque le contexte de liaison pour jQuery est utilisé, utilisez jQuery.proxy [docs] . 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: Utiliser les fonctions de flèche

ECMAScript 6 introduit des fonctions de flèche , qui peuvent être considérées comme des fonctions lambda. Ils n'ont pas leur propre liaison. Au lieu de cela, this est considéré 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));
}

Définir this du rappel - partie 2

Certaines fonctions / méthodes qui acceptent les rappels acceptent également une valeur à laquelle le rappel doit 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 callback et le second argument est la valeur à laquelle 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: Le fait de pouvoir ou non transmettre une valeur pour this est généralement mentionné dans la documentation de cette fonction / méthode. Par exemple, la méthode $.ajax [docs] de jQuery 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ée en tant que gestionnaire d'événement click, mais si vous cliquez sur document.body , la valeur enregistrée sera undefined , car dans le gestionnaire d'événements, this fait référence à document.body , pas à l'instance de Foo .
Comme cela a déjà été mentionné au début, this dépend de la façon dont la fonction est appelée et non de la manière dont elle est définie .
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 la même que celle mentionnée ci-dessus: Si disponible, utilisez .bind pour lier explicitement this valeur à 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 callback / event 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();

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 propriété de data de l'objet créé dans le rappel. 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 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?


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

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

1. Utilisez la fonction bind()

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

Nous ne pouvons pas lier cela à setTimeout() , car il s'exécute toujours avec l'objet global (Window) , si vous voulez accéder à this contexte dans la fonction callback puis en utilisant bind() à la fonction callback que nous pouvons réaliser comme:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);

Tout d'abord, vous devez avoir une compréhension claire de la scope et du comportement de this mot this clé dans le contexte de la 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 this clé dans la portée globale fait référence à l'objet window. this fonction à l'intérieur fait aussi référence à l'objet fenêtre. Donc, 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)

}

sayNameVersionX maintenant une instance à partir du constructeur de la personne et sayNameVersionX différentes versions de la sayNameVersionX (X fait référence à 1,2,3,4) avec niceCallback pour voir de combien de façons nous pouvons manipuler this callback interne pour faire référence à l'instance de person .

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 jeu de mots this clés this à la valeur fournie.

sayNameVersion1 et sayNameVersion2 utilisent bind pour manipuler this de la fonction de rappel.

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

le premier lier this avec le rappel à l'intérieur de la méthode elle-même. Et pour le second un callback est passé avec l'objet lié à lui.

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 de la méthode call est utilisé comme this dans la fonction invoquée avec l' call attaché.

sayNameVersion3 utilise call pour manipuler this pour faire référence à l'objet person 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 :

Similaire à call , le premier argument de apply fait référence à l'objet qui sera indiqué par this mot this clé.

sayNameVersion4 utilisations de sayNameVersion4 apply pour manipuler this pour se référer à l'objet personnel

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

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

p1.sayNameVersion4(niceCallback)




this