Como acessar o `this` correto dentro de um callback?
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 data
propriedade do objeto criado dentro do retorno de chamada. Parece que this
nã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
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 this
ligaçã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 this
em 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 self
e that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Por self
ser 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 this
valor do próprio retorno de chamada.
Definido explicitamente this
o retorno de chamada - parte 1
Pode parecer que você não tem controle sobre o valor de this
porque 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 this
associada a um valor. A função tem exatamente o mesmo comportamento daquela que você chamou .bind
, só que this
foi configurada por você. Não importa como ou quando essa função é chamada, this
sempre 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 this
ao 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 this
do 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 this
deve 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 this
ao 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, this
geralmente é 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.body
for clicado, o valor registrado será undefined
, porque dentro do manipulador de eventos, this
refere-se ao document.body
, não à instância de Foo
.
Como já mencionado no início, o que this
se 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 .bind
para vincular explicitamente this
a 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();
Aqui estão várias maneiras de acessar o contexto pai dentro do contexto filho -
- Você pode usar a
bind()
função. - Armazene a referência ao contexto / this dentro de outra variável (veja o exemplo abaixo).
- Use as funções de seta ES6 .
- 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);
});
}
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 $.proxy
método, pois bind
não é compatível com todos os navegadores:
this.saveNextLevelData($.proxy(this.setAll, this));
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.
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);
Primeiro, você precisa ter uma compreensão clara scope
e comportamento da this
palavra-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. this
palavra-chave no escopo global refere-se ao objeto janela. this
a função dentro também se refere ao objeto da janela. Portanto this
, sempre se referirá à janela até encontrarmos uma maneira de manipular this
para 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 this
funções de retorno de chamada:
Aqui eu tenho uma função construtora chamada Person. Ele tem uma propriedade chamada name
e 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 sayNameVersionX
método (X refere-se a 1,2,3,4) niceCallback
para ver quantas maneiras podemos manipular o this
retorno de chamada interno para se referir à person
instâ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 this
palavra - chave definida para o valor fornecido.
sayNameVersion1
e sayNameVersion2
use bind para manipular this
a função de retorno de chamada.
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
o primeiro vincula this
com 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 argument
do call
método é usado como this
dentro da função que é chamada com call
anexado a ele.
sayNameVersion3
usa call
para manipular o this
para 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 apply
refere-se ao objeto que será indicado pela this
palavra-chave.
sayNameVersion4
usa apply
para manipular this
para 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)
Não podemos vincular isso a setTimeout()
, como sempre executa com objeto global (janela) , se você deseja acessar o this
contexto 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);
A questão gira em torno de como a this
palavra-chave se comporta em javascript. this
se comporta de forma diferente como abaixo,
- O valor de
this
geralmente é determinado por um contexto de execução de função. - No escopo global,
this
refere-se ao objeto global (owindow
objeto). - Se o modo estrito estiver habilitado para qualquer função, o valor de
this
seráundefined
como no modo estrito, o objeto global se refereundefined
no lugar dowindow
objeto. - O objeto que está antes do ponto é o que esta palavra-chave será associada.
- Podemos definir o valor desta explicitamente
call()
,bind()
eapply()
- When the
new
keyword is used (a constructor), this is bound to the new object being created. - 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:
- https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
- arrow-functions-vs-bind
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).
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
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':
- 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 - 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 thethis
keyword here because it is less explicit than using something likewindow
! - There exist certain constructs like arrow functions and functions created using the
Function.prototype.bind()
a function that can fix the value ofthis
. These are exceptions of the rule but are really helpful to fix the value ofthis
.
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):
this
isobj2
because of the left of the dot rule, we can see howtest1
is calledobj2.test1();
.obj2
is left of the dot and thus thethis
value.- Even though
obj2
is left of the dot,test2
is bound toobj1
via thebind()
method. So thethis
value isobj1
. obj2
is left of the dot from the function which is called:obj2.test3()
. Thereforeobj2
will be the value ofthis
.- In this case:
obj2.test4()
obj2
is left of the dot. However, arrow functions don't have their ownthis
binding. Therefore it will bind to thethis
value of the outer scope which is themodule.exports
an object which was logged in the beginning. - We can also specify the value of
this
by using thecall
function. Here we can pass in the desiredthis
value as an argument, which isobj2
in this case.
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
}