Descobrir o valor de this
pode ser complicado em JavaScript. Veja como fazer isso...
A this
do JavaScript é o motivo de muitas piadas, e isso é muito complicado.
No entanto, já vi desenvolvedores fazendo coisas muito mais complicadas e específicas de domínio para evitar lidar
com esse this
. Se você tiver dúvidas sobre this
, esperamos que 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
é semelhante a uma if (…) … else if () … else if (…) …
grande, então você pode ir direto para a primeira
seção que corresponde ao código que está visualizando.
- 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
"vinculado" - Caso contrário, se
this
for definido no momento da chamada - 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 estrito
- Caso contrário
Se a função estiver definida como uma função de seta:
const arrowFunction = () => {
console.log(this);
};
Nesse caso, o valor de this
é sempre o mesmo que 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 mudado. 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 "Vinculado"
Com os 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
da 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).
O exemplo acima pode parecer que está quebrando a regra "this
será igual a 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);
};
}
}
Os padrões alternativos envolvem vincular uma função existente no construtor ou atribuir a função no construtor. Se você não puder usar os 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 comando acima chamará Whatever
(ou a função de construtor, 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 de estilo mais antigo:
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
"vinculado":
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Sempre que boundFunction
for chamado, o valor this
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 mudado 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 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, o this
é definido como 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 acima. 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, então 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 como:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Isso falha porque a implementação de querySelector
analisa o próprio valor de this
e espera
que seja um nó DOM de classificações, 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. Os métodos do console, como console.log
, foram modificados para
evitar referências de this
. Portanto, log
não precisa estar vinculado a console
Caso contrário, se a função ou o escopo pai estiver no modo estrito:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
Nesse caso, o valor de this
é indefinido. 'use strict'
não será necessário na função se o
escopo pai estiver no modo
estrito (e todos
os módulos estiverem no modo estrito).
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
. Alguma pergunta? Perdi alguma coisa? Fique
à vontade para twittar para mim.
Agradecemos a Mathias Bynens, Ingvar Stepanyan e Thomas Steiner pela análise.