Descobrir o valor de this pode ser complicado no JavaScript. Confira como fazer isso:
O this do JavaScript é motivo de muitas piadas, e isso acontece porque ele é bem complicado.
No entanto, já vi desenvolvedores fazerem coisas muito mais complicadas e específicas do domínio para evitar lidar
com esse this. Se você não tiver certeza sobre this, talvez isso ajude. Este é meu guia de this.
Vou começar com a situação mais específica e terminar com a menos específica. Este artigo
é como um grande if (…) … else if () … else if (…) …, então você pode ir direto para a primeira
seção que corresponde ao código que você está vendo.
- Se a função for definida como uma função de seta
- Caso contrário, se a função/classe for chamada com
new - Caso contrário, se a função tiver um valor
this"limitado" - Caso contrário, se
thisfor definido no call-time - Caso contrário, se a função for chamada por um objeto pai (
parent.func()) - Caso contrário, se a função ou o escopo pai estiver no modo restrito
- Caso contrário
Se a função for definida como uma função de seta:
const arrowFunction = () => {
console.log(this);
};
Nesse caso, o valor de this é sempre igual a this no escopo pai:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
As funções de seta são ótimas porque o valor interno de this não pode ser alterado. Ele sempre é o mesmo
que o this externo.
Outros exemplos
Com as funções de seta, o valor de this não pode ser alterado com bind:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Com as funções de seta, o valor de this não pode ser alterado com call ou apply:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
Com as funções de seta, o valor de this não pode ser alterado chamando a função como membro de
outro objeto:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Com as funções de seta, o valor de this não pode ser alterado chamando a função como um
construtor:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Métodos de instância "Bound"
Com métodos de instância, se você quiser garantir que this sempre se refira à instância da classe, a melhor
maneira é usar funções de seta e campos
de classe:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Esse padrão é muito útil ao usar métodos de instância como listeners de eventos em componentes (como componentes do React ou da Web).
O exemplo acima pode parecer que está violando a regra "this será o mesmo que this no escopo pai",
mas começa a fazer sentido se você pensar nos campos de classe como açúcar sintático para definir coisas
no construtor:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
Padrões alternativos envolvem vincular uma função existente no construtor ou atribuir a função no construtor. Se você não puder usar campos de classe por algum motivo, atribuir funções no construtor é uma alternativa razoável:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
Caso contrário, se a função/classe for chamada com new:
new Whatever();
O código acima vai chamar Whatever (ou a função de construtor dela, se for uma classe) com this definido como o
resultado de Object.create(Whatever.prototype).
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
O mesmo vale para construtores mais antigos:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Outros exemplos
Quando chamado com new, o valor de this não pode ser alterado com bind:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Quando chamado com new, o valor de this não pode ser alterado chamando a função como membro
de outro objeto:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Caso contrário, se a função tiver um valor this "limitado":
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Sempre que boundFunction for chamado, o valor this dele será o objeto transmitido para bind
(boundObject).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Outros exemplos
Ao chamar uma função vinculada, o valor de this não pode ser alterado com call ou
apply:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
Ao chamar uma função vinculada, o valor de this não pode ser alterado chamando a função como um
membro de outro objeto:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Caso contrário, se this for definido no momento da chamada:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
O valor de this é o objeto transmitido para call/apply.
Infelizmente, this é definido como algum outro valor por elementos como listeners de eventos do DOM, e o uso dele pode
resultar em um código difícil de entender:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
Evito usar this em casos como o acima e, em vez disso:
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); });
Caso contrário, se a função for chamada por um objeto pai (parent.func()):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
Nesse caso, a função é chamada como membro de obj, então this será obj. Isso acontece no
momento da chamada. Portanto, o link será quebrado se a função for chamada sem o objeto pai ou com um
objeto pai diferente:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj é falso porque someMethod não é chamado como membro de obj. Talvez você
tenha encontrado esse problema ao tentar algo como este:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Isso falha porque a implementação de querySelector verifica o próprio valor this e espera
que ele seja um tipo de nó DOM, e o exemplo acima interrompe essa conexão. Para fazer isso corretamente:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Curiosidade: nem todas as APIs usam this internamente. Métodos de console como console.log foram alterados para
evitar referências a this. Portanto, log não precisa ser vinculado a console.
Caso contrário, se a função ou o escopo pai estiver no modo restrito:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
Nesse caso, o valor de this é indefinido. 'use strict' não é necessário na função se o
escopo pai estiver no modo
rígido (e todos
os módulos estiverem no modo rígido).
Se esse não for seu caso, faça o seguinte:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
Nesse caso, o valor de this é o mesmo que globalThis.
Ufa.
Pronto. Isso é tudo que sei sobre this. Dúvidas? Perdeu alguma coisa? Fique
à vontade para me enviar um tweet.
Agradecemos a Mathias Bynens, Ingvar Stepanyan e Thomas Steiner pela revisão.