Como acessar o `this` correto dentro de um callback?

Nov 29 2013

Eu tenho uma função construtora que registra um manipulador de eventos:

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

No entanto, não consigo acessar a datapropriedade do objeto criado dentro do retorno de chamada. Parece que thisnão se refere ao objeto que foi criado, mas a outro.

Também tentei usar um método de objeto em vez de uma função anônima:

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

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

mas exibe os mesmos problemas.

Como posso acessar o objeto correto?

Respostas

1910 FelixKling Nov 29 2013 at 13:13

O que você deve saber sobre this

this(também conhecido como "o contexto") é uma palavra-chave especial dentro de cada função e seu valor depende apenas de como a função foi chamada, não como / quando / onde foi definida. Não é afetado por escopos lexicais como outras variáveis ​​(exceto para funções de seta, veja abaixo). aqui estão alguns exemplos:

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`

Para saber mais sobre this, dê uma olhada na documentação do MDN .


Como se referir ao correto this

Use as funções de seta

ECMAScript 6 introduziu funções de seta , que podem ser consideradas funções lambda. Eles não têm sua própria thisligação. Em vez disso, thisé pesquisado no escopo como uma variável normal. Isso significa que você não precisa ligar .bind. Esse não é o único comportamento especial que eles têm. Consulte a documentação do MDN para obter mais informações.

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

Não use this

Na verdade, você não deseja acessar thisem particular, mas sim o objeto a que se refere . É por isso que uma solução fácil é simplesmente criar uma nova variável que também se refira a esse objeto. A variável pode ter qualquer nome, mas os mais comuns são selfe that.

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

Por selfser uma variável normal, ela obedece às regras de escopo lexical e é acessível dentro do callback. Isso também tem a vantagem de você poder acessar o thisvalor do próprio retorno de chamada.

Definido explicitamente thiso retorno de chamada - parte 1

Pode parecer que você não tem controle sobre o valor de thisporque seu valor é definido automaticamente, mas esse não é o caso.

Cada função possui o método .bind [docs] , que retorna uma nova função thisassociada a um valor. A função tem exatamente o mesmo comportamento daquela que você chamou .bind, só que thisfoi configurada por você. Não importa como ou quando essa função é chamada, thissempre fará referência ao valor passado.

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

Nesse caso, estamos associando o retorno de chamada thisao valor de MyConstructor's this.

Nota: Quando um contexto de ligação para jQuery, use jQuery.proxy [docs] em seu lugar. O motivo para fazer isso é para que você não precise armazenar a referência à função ao desvincular um retorno de chamada de evento. O jQuery lida com isso internamente.

Conjunto thisdo retorno de chamada - parte 2

Algumas funções / métodos que aceitam retornos de chamada também aceitam um valor ao qual o retorno de chamada thisdeve se referir. Isso é basicamente o mesmo que vincular você mesmo, mas a função / método faz isso por você. Array#map [docs] é esse método. Sua assinatura é:

array.map(callback[, thisArg])

O primeiro argumento é o retorno de chamada e o segundo argumento é o valor thisao qual se deve referir. Aqui está um exemplo inventado:

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

Nota: Se você pode ou não passar um valor para, thisgeralmente é mencionado na documentação dessa função / método. Por exemplo, o $.ajaxmétodo jQuery [docs] descreve uma opção chamada context:

Este objeto se tornará o contexto de todos os callbacks relacionados ao Ajax.


Problema comum: usando métodos de objeto como retornos de chamada / manipuladores de eventos

Outra manifestação comum desse problema é quando um método de objeto é usado como retorno de chamada / manipulador de eventos. Funções são cidadãos de primeira classe em JavaScript e o termo "método" é apenas um termo coloquial para uma função que é um valor de uma propriedade de objeto. Mas essa função não tem um link específico para seu objeto "recipiente".

Considere o seguinte exemplo:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

A função this.methodé atribuída como manipulador de eventos de clique, mas se o document.bodyfor clicado, o valor registrado será undefined, porque dentro do manipulador de eventos, thisrefere-se ao document.body, não à instância de Foo.
Como já mencionado no início, o que thisse refere a depende de como a função é chamada , não de como é definida .
Se o código for como o seguinte, pode ser mais óbvio que a função não tem uma referência implícita ao objeto:

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

A solução é a mesma mencionada acima: se disponível, use .bindpara vincular explicitamente thisa um valor específico

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

ou chame explicitamente a função como um "método" do objeto, usando uma função anônima como callback / manipulador de eventos e atribua o objeto ( this) a outra variável:

var self = this;
document.body.onclick = function() {
    self.method();
};

ou use uma função de seta:

document.body.onclick = () => this.method();
225 MohanDere Aug 13 2016 at 17:26

Aqui estão várias maneiras de acessar o contexto pai dentro do contexto filho -

  1. Você pode usar a bind()função.
  2. Armazene a referência ao contexto / this dentro de outra variável (veja o exemplo abaixo).
  3. Use as funções de seta ES6 .
  4. Altere o design / arquitetura do código / função - para isso, você deve ter o comando sobre os padrões de design em javascript.

1. bind()Função de uso

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

Se você estiver usando underscore.js-http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));

2 Armazene a referência ao contexto / isto dentro de outra variável

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

Função de 3 setas

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}
59 Guffa May 21 2014 at 07:11

Está tudo na sintaxe "mágica" de chamar um método:

object.property();

Quando você obtém a propriedade do objeto e a chama de uma vez, o objeto será o contexto para o método. Se você chamar o mesmo método, mas em etapas separadas, o contexto será o escopo global (janela):

var f = object.property;
f();

Quando você obtém a referência de um método, ele não está mais anexado ao objeto, é apenas uma referência a uma função simples. O mesmo acontece quando você obtém a referência para usar como retorno de chamada:

this.saveNextLevelData(this.setAll);

É onde você vincularia o contexto à função:

this.saveNextLevelData(this.setAll.bind(this));

Se você estiver usando jQuery, você deve usar o $.proxymétodo, pois bindnão é compatível com todos os navegadores:

this.saveNextLevelData($.proxy(this.setAll, this));
34 RobG Jun 01 2014 at 07:44

O problema com o "contexto"

O termo "contexto" às vezes é usado para se referir ao objeto referenciado por este . Seu uso é inapropriado porque não se encaixa semanticamente ou tecnicamente com o this do ECMAScript .

"Contexto" significa as circunstâncias em torno de algo que adiciona significado, ou alguma informação anterior e seguinte que dá significado extra. O termo "contexto" é usado no ECMAScript para se referir ao contexto de execução , que é todos os parâmetros, escopo e isso dentro do escopo de algum código em execução.

Isso é mostrado na ECMA-262 seção 10.4.2 :

Defina ThisBinding com o mesmo valor que ThisBinding do contexto de execução de chamada

o que indica claramente que isso faz parte de um contexto de execução.

Um contexto de execução fornece as informações circundantes que adicionam significado ao código que está sendo executado. Inclui muito mais informações do que apenas thisBinding .

Portanto, o valor disso não é "contexto", é apenas uma parte de um contexto de execução. É essencialmente uma variável local que pode ser definida pela chamada a qualquer objeto e no modo estrito, a qualquer valor.

32 Ashish Jan 30 2019 at 18:01

Você deve saber sobre "esta" palavra-chave.

De acordo com minha opinião, você pode implementar "isso" de três maneiras (função Self / Arrow / Método Bind)

Esta palavra-chave de função se comporta de maneira um pouco diferente em JavaScript em comparação com outras linguagens.

Ele também tem algumas diferenças entre o modo estrito e o modo não estrito.

Na maioria dos casos, o valor disso é determinado por como uma função é chamada.

Não pode ser definido por atribuição durante a execução e pode ser diferente a cada vez que a função é chamada.

ES5 introduziu o método bind () para definir o valor do this de uma função, independentemente de como ela é chamada,

e o ES2015 introduziu funções de seta que não fornecem sua própria ligação (ela retém esse valor do contexto lexical envolvente).

Método 1: Self - Self está sendo usado para manter uma referência ao original, mesmo quando o contexto está mudando. É uma técnica frequentemente usada em manipuladores de eventos (especialmente em encerramentos).

Referência :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étodo 2 : função de seta - uma expressão de função de seta é uma alternativa sintaticamente compacta para uma expressão de função regular,

embora sem suas próprias ligações às palavras-chave this, arguments, super ou new.target.

Expressões de função de seta não são adequadas como métodos e não podem ser usadas como construtores.

Referência :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étodo 3 : Bind- O método bind () cria uma nova função que,

quando chamado, tem esta palavra-chave definida com o valor fornecido,

com uma determinada sequência de argumentos precedendo qualquer fornecido quando a nova função é chamada.

Referência: 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);
26 AL-zami Aug 19 2017 at 00:58

Primeiro, você precisa ter uma compreensão clara scopee comportamento da thispalavra-chave no contexto de scope.

this& scope:


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

em suma, o escopo global se refere ao objeto da janela. As variáveis ​​declaradas em um escopo global são acessíveis de qualquer lugar. Por outro lado, o escopo da função reside dentro de uma função. A variável declarada dentro de uma função não pode ser acessada do mundo externo normalmente. thispalavra-chave no escopo global refere-se ao objeto janela. thisa função dentro também se refere ao objeto da janela. Portanto this, sempre se referirá à janela até encontrarmos uma maneira de manipular thispara indicar um contexto de nossa própria escolha.

--------------------------------------------------------------------------------
-                                                                              -
-   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                                              -
--------------------------------------------------------------------------------

Diferentes maneiras de manipular thisfunções de retorno de chamada:

Aqui eu tenho uma função construtora chamada Person. Ele tem uma propriedade chamada namee quatro método chamado sayNameVersion1, sayNameVersion2, sayNameVersion3, sayNameVersion4. Todos os quatro têm uma tarefa específica. Aceite um retorno de chamada e invoque-o. O retorno de chamada tem uma tarefa específica que é registrar a propriedade name de uma instância da função construtora 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)

}

Agora vamos criar uma instância do construtor de pessoa e invocar diferentes versões do sayNameVersionXmétodo (X refere-se a 1,2,3,4) niceCallbackpara ver quantas maneiras podemos manipular o thisretorno de chamada interno para se referir à personinstância.

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

ligar :

O que o bind faz é criar uma nova função com a thispalavra - chave definida para o valor fornecido.

sayNameVersion1e sayNameVersion2use bind para manipular thisa função de retorno de chamada.

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

o primeiro vincula thiscom o retorno de chamada dentro do próprio método. E, para o segundo, um retorno de chamada é passado com o objeto vinculado a ele.

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

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

chamar :

O first argumentdo callmétodo é usado como thisdentro da função que é chamada com callanexado a ele.

sayNameVersion3usa callpara manipular o thispara se referir ao objeto pessoa que criamos, em vez do objeto janela.

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

e é chamado assim:

p1.sayNameVersion3(niceCallback)

Aplique :

Semelhante a call, o primeiro argumento de applyrefere-se ao objeto que será indicado pela thispalavra-chave.

sayNameVersion4usa applypara manipular thispara se referir ao objeto pessoa

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

e é chamado da seguinte forma. Simplesmente, o retorno de chamada é passado,

p1.sayNameVersion4(niceCallback)
20 DattaChanewad Nov 17 2017 at 21:32

Não podemos vincular isso a setTimeout(), como sempre executa com objeto global (janela) , se você deseja acessar o thiscontexto na função de retorno de chamada, então, usando bind()a função de retorno de chamada, podemos alcançar como:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
13 Code_Mode Feb 18 2019 at 16:10

A questão gira em torno de como a thispalavra-chave se comporta em javascript. thisse comporta de forma diferente como abaixo,

  1. O valor de thisgeralmente é determinado por um contexto de execução de função.
  2. No escopo global, thisrefere-se ao objeto global (o windowobjeto).
  3. Se o modo estrito estiver habilitado para qualquer função, o valor de thisserá undefinedcomo no modo estrito, o objeto global se refere undefinedno lugar do windowobjeto.
  4. O objeto que está antes do ponto é o que esta palavra-chave será associada.
  5. Podemos definir o valor desta explicitamente call(), bind()eapply()
  6. When the new keyword is used (a constructor), this is bound to the new object being created.
  7. Arrow Functions don’t bind this — instead, this is bound lexically (i.e. based on the original context)

As most of the answers suggest, we can use Arrow function or bind() Method or Self var. I would quote a point about lambdas (Arrow function) from Google JavaScript Style Guide

Prefer using arrow functions over f.bind(this), and especially over goog.bind(f, this). Avoid writing const self = this. Arrow functions are particularly useful for callbacks, which sometimes pass unexpectedly additional arguments.

Google clearly recommends using lambdas rather than bind or const self = this

So the best solution would be to use lambdas as below,

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

References:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. arrow-functions-vs-bind
8 skyboyer Sep 22 2018 at 20:38

Currently there is another approach possible if classes are used in code.

With support of class fields it's possible to make it next way:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

For sure under the hood it's all old good arrow function that bind context but in this form it looks much more clear that explicit binding.

Since it's Stage 3 Proposal you will need babel and appropriate babel plugin to process it as for now(08/2018).

5 AndreaPuddu Aug 28 2018 at 16:10

Another approach, which is the standard way since DOM2 to bind this within the event listener, that let you always remove the listener (among other benefits), is the handleEvent(evt)method from the EventListener interface:

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

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

Detailed information about using handleEvent can be found here: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

2 WillemvanderVeen May 05 2020 at 15:48

this in JS:

The value of this in JS is 100% determined by how a function is called, and not how it is defined. We can relatively easily find the value of this by the 'left of the dot rule':

  1. When the function is created using the function keyword the value of this is the object left of the dot of the function which is called
  2. If there is no object left of the dot then the value of this inside a function is often the global object (global in node, window in browser). I wouldn't recommend using the this keyword here because it is less explicit than using something like window!
  3. There exist certain constructs like arrow functions and functions created using the Function.prototype.bind() a function that can fix the value of this. These are exceptions of the rule but are really helpful to fix the value of this.

Example in 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);

Output:

Let me walk you through the outputs 1 by 1 (ignoring the first log starting from the second):

  1. this is obj2 because of the left of the dot rule, we can see how test1 is called obj2.test1();. obj2 is left of the dot and thus the this value.
  2. Even though obj2 is left of the dot, test2 is bound to obj1 via the bind() method. So the this value is obj1.
  3. obj2 is left of the dot from the function which is called: obj2.test3(). Therefore obj2 will be the value of this.
  4. In this case: obj2.test4() obj2 is left of the dot. However, arrow functions don't have their own this binding. Therefore it will bind to the this value of the outer scope which is the module.exports an object which was logged in the beginning.
  5. We can also specify the value of this by using the call function. Here we can pass in the desired this value as an argument, which is obj2 in this case.
1 Md.TazbirUrRahmanBhuiyan Oct 12 2020 at 23:07

I was facing problem with Ngx line chart xAxisTickFormatting function which was called from HTML like this: [xAxisTickFormatting]="xFormat". I was unable to access my component's variable from the function declared. This solution helped me to resolve the issue to find the correct this. Hope this helps the Ngx line chart, users.

instead of using the function like this:

xFormat (value): string {
  return value.toString() + this.oneComponentVariable; //gives wrong result 
}

Use this:

 xFormat = (value) => {
   // console.log(this);
   // now you have access to your component variables
   return value + this.oneComponentVariable
 }