web-dev-qa-db-fra.com

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 peux pas accéder à la propriété data de l'objet créé à l'intérieur du rappel. Il semble que this ne se réfère pas à l'objet qui a été créé, mais à un autre.

J'ai aussi 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 au bon objet?

1093
Felix Kling

Ce que vous devez savoir sur this

this (ou "le contexte") est un mot clé spécial à l'intérieur de chaque fonction et sa valeur dépend uniquement de comment la fonction a été appelée, pas comment/quand/où a été défini. Il n'est pas affecté par les portées lexicales comme les autres variables (sauf pour les fonctions de flèche, 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, jetez un œil à documentation MDN .


Comment se référer à la bonne this

N'utilisez pas 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 se réfère également à 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);
    });
}

Comme self est une variable normale, elle obéit aux règles de portée lexicale et est accessible à l'intérieur du rappel. Cela présente également l’avantage de pouvoir accéder à la valeur this du rappel lui-même.

Définir explicitement this du rappel - partie 1

Il semblerait que vous n’ayez aucun contrôle sur la valeur de this car sa valeur est définie automatiquement, mais ce n’est en réalité pas le cas.

Chaque fonction a la méthode .bind[docs] , qui renvoie une nouvelle fonction avec this liée à une valeur. La fonction a exactement le même comportement que celle que vous avez appelée .bind on, seulement 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 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 this du rappel à la valeur de MyConstructor 'this.

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

ECMAScript 6: Utiliser fonctions de flèche

ECMAScript 6 introduit les fonctions de flèche , que l’on peut considérer comme des fonctions lambda. Ils n'ont pas leur propre lien this. Au lieu de cela, this est recherché dans la portée 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, référez-vous à 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 this du rappel devrait faire référence. C'est fondamentalement la même chose que de 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 à laquelle this devrait 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

Remarque: Le fait que vous puissiez ou non transmettre une valeur pour this est généralement mentionné dans la documentation de cette fonction/méthode. Par exemple, méthode $.ajax 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 en tant que callbacks/gestionnaires d'événements

Une autre manifestation courante de ce problème est lorsqu'une méthode d'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 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 déjà mentionné au début, ce à quoi this dépend de la façon dont la fonction est appelée et non de la manière dont elle est défini .
Si le code ressemblait à ceci, il serait peut-être plus évident que la fonction n’ait 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 identique à celle mentionnée ci-dessus: Si disponible, utilisez .bind pour lier explicitement this à une valeur spécifique.

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

ou explicitement appeler la fonction en tant que "méthode" de l'objet, en utilisant une fonction anonyme en tant que callback/gestionnaire d'événements et attribuer 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();
1602
Felix Kling

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

  1. Vous pouvez utiliser la fonction bind()
  2. Enregistre la référence au contexte/ceci dans une autre variable (voir l'exemple ci-dessous).
  3. Utilisez les fonctions ES6 Arrow .
  4. Modifiez le code/la fonction/l’architecture - pour cela, vous devez avoir la maîtrise de 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 Stocke 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);
  });
}

Fonction 3 flèches

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

Tout est dans la syntaxe "magique" d'appeler une méthode:

object.property();

Lorsque vous obtenez la propriété de l'objet et que vous l'appelez d'un coup, l'objet sera le contexte de la méthode. Si vous appelez la même méthode, mais à des étapes distinctes, le contexte correspond à la portée globale (fenêtre):

var f = object.property;
f();

Lorsque vous obtenez la référence d'une méthode, celle-ci n'est plus attachée à l'objet, il s'agit simplement d'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 devriez plutôt utiliser la méthode $.proxy, car bind n'est pas pris en charge par tous les navigateurs:

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

Le problème avec "contexte"

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

"Contexte" désigne les circonstances entourant quelque chose qui ajoute un sens, ou des informations précédentes et suivantes qui donnent un sens supplémentaire. Le terme "contexte" est utilisé dans ECMAScript pour faire référence à execution context , qui comprend tous les paramètres, la portée et this dans la portée de certains codes d'exécution.

Ceci est montré dans ECMA-262 section 10.4.2 :

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

ce qui indique clairement que this 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 inclut beaucoup plus d'informations que juste le thisBinding .

Donc, la valeur de this n'est pas "context", ce n'est qu'une partie d'un contexte d'exécution. Il s’agit essentiellement d’une variable locale qui peut être définie par l’appel de n’importe quel objet et en mode strict, pour n’importe quelle valeur. 

22
RobG

Tout d'abord, vous devez avoir une compréhension claire de scope et du comportement du mot clé this 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.variable déclarée à l'intérieur d'une fonction ne peut pas être accédée de l'extérieur du monde normalement .this Le mot clé dans la portée globale fait référence à l'objet fenêtre .this La fonction intérieure fait également référence à l'objet fenêtre. Ainsi, this fera toujours référence à 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 à l'intérieur des fonctions de rappel:

Ici, j'ai une fonction constructeur appelée Personne. Il a une propriété appelée name et quatre méthodes appelées sayNameVersion1, sayNameVersion2, sayNameVersion3, sayNameVersion4. Tous les quatre ont une tâche spécifique. Accepter un rappel et l'invoquer. Le rappel a une tâche spécifique qui consiste à consigner la propriété name d'une instance de la fonction du 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 la personne et appelons différentes versions de la méthode sayNameVersionX (X fait référence à 1,2,3,4) avec niceCallback pour voir de nombreuses façons dont nous pouvons manipuler le this dans callback pour faire référence à l'instance person.

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

lier :

Qu'est-ce que bind fait est de créer une nouvelle fonction avec le mot clé this défini sur 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()
}

la première se lie this avec un rappel à l'intérieur de la méthode elle-même.Et pour le deuxième 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

appel :

Le first argument de la méthode call est utilisé en tant que this dans la fonction appelée avec call qui lui est associé.

sayNameVersion3 utilise call pour manipuler this pour 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, le premier argument de apply fait référence à l'objet qui sera indiqué par le mot clé this.

sayNameVersion4 utilise apply pour manipuler this pour faire référence à un objet personne

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

et il s’appelle comme suit. Il suffit de rappeler le rappel,

p1.sayNameVersion4(niceCallback)
18
AL-zami

Nous ne pouvons pas lier ceci à setTimeout(), car il est toujours exécuté avec objet global (Window) , si vous souhaitez accéder au contexte this dans la fonction de rappel, utilisez bind() pour la fonction de rappel que vous pouvez obtenir comme suit:

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

Vous devriez connaître le mot-clé "this". 

Selon mon point de vue, vous pouvez implémenter "ceci" de trois manières(Self/Fonction Flèche/Méthode Bind)

Une fonction de ce mot clé se comporte un peu différemment en JavaScript par rapport aux autres langues.

Il existe également des différences entre le mode strict et le mode non strict.

Dans la plupart des cas, la valeur de cela dépend de la manière dont une fonction est appelée.

Il ne peut pas être défini par affectation lors de l'exécution et il peut être différent à chaque appel de la fonction.

ES5 a introduit la méthode bind () pour définir la valeur d'une fonction, quelle que soit sa dénomination.

et ES2015 a introduit des fonctions de flèche qui ne fournissent pas leur propre lien (il conserve la valeur this du contexte lexical englobant).

Method1: Self - Self est utilisé pour conserver une référence à l'original même si 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);
    });
}

Method2: 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 à this, arguments, super, ou new.target, mots-clés.

Les expressions de fonction de flèche sont mal adaptées en tant que méthodes et ne peuvent pas être utilisées en tant que 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);
    });
}

Method3: Bind- La méthode bind () crée une nouvelle fonction qui,

lorsqu'il est appelé, son mot-clé this est défini sur la valeur fournie,

avec une séquence d'arguments donnée précédant n'importe laquelle fournie 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);
8
ashish

Une autre approche, qui est méthode standard depuis DOM2 pour lier this au sein de l'écouteur d'événements, qui vous permet de toujours supprimer l'écouteur (entre autres avantages), est la méthode handleEvent(evt) de l'interface EventListener. :

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

Des informations détaillées sur l’utilisation de handleEvent sont disponibles ici: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

2
Andrea Puddu

Actuellement, une autre approche est possible si les classes sont utilisées dans le code. 

Avec le support de class fields , il est possible de le faire 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, ce sont toutes de vieilles bonnes fonctions de flèche qui lient le contexte, mais sous cette forme, cela semble beaucoup plus clair que la liaison explicite.

Comme il s'agit de la proposition de stade 3, vous aurez besoin de babel et du plugin babel approprié pour le traiter comme pour le moment (08/2018).

2
skyboyer

La question porte sur le comportement du mot clé this en javascript. this se comporte différemment comme ci-dessous,

  1. La valeur de ceci est généralement déterminée par un contexte d'exécution de fonctions.
  2. Dans la portée globale, cela fait référence à l'objet global (l'objet window).
  3. Si le mode strict est activé pour n'importe quelle fonction, la valeur de «this» sera «indéfinie», car en mode strict, objet global désigne un objet non défini à la place de l'objet windows.
  4. L'objet qui se trouve devant le point est ce à quoi le mot-clé this sera lié.
  5. Nous pouvons en définir explicitement la valeur avec call (), bind () et apply ()
  6. Lorsque le nouveau mot clé est utilisé (un constructeur), il est lié au nouvel objet en cours de création.
  7. Les fonctions de flèche ne lient pas cela - à la place, cela est lié lexicalement (c’est-à-dire basé sur le contexte original)

Comme le suggère la plupart des réponses, nous pouvons utiliser la fonction Arrow ou bind() Method ou Self var. Je voudrais citer un point sur lambdas (fonction Arrow) de Google JavaScript Style Guide

Préfère utiliser les fonctions fléchées sur f.bind (this), et surtout sur goog.bind (f, this). Évitez d'écrire const auto = this. Fonctions fléchées sont particulièrement utiles pour les rappels, qui passent parfois inattendus arguments supplémentaires.

Google recommande clairement d'utiliser lambdas plutôt que bind ou const self = this

Donc, la meilleure solution serait d’utiliser des lambdas comme ci-dessous,

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

Références:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. arrow-functions-vs-bind
0
Code_Mode