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 if (…) … else if () … else if (…) …
grande, então você pode ir direto para a primeira
seção que corresponde ao código que você está vendo.
- Se a função estiver definida como uma arrow function
- 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
this
for definido no call-time - Caso contrário, se a função for chamada usando um objeto pai (
parent.func()
) - Caso contrário, se a função ou o escopo pai estiver no modo estrito
- Caso contrário
Se a função estiver definida como uma seta de função:
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âncias "vinculadas"
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 componentes da Web).
Pode parecer que isso está quebrando 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 uma facilidade sintática 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);
};
}
}
Os padrões alternativos envolvem a vinculação de uma função existente no construtor ou a atribuição da 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 um 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
"bound":
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 horário 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
foi definido com algum outro valor por coisas como listeners de eventos 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 usando um objeto pai (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
Nesse caso, a função é chamada como um membro de obj
, então this
será obj
. Isso acontece no
tempo da chamada. Portanto, o link será corrompido 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 assim:
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);
Divertido fato: 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 rigoroso:
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.