JavaScript: Qual é o significado disso?
Descobrir o valor de this
pode ser complicado em JavaScript. Veja como fazer…
O this
do JavaScript é o alvo de muitas piadas, e isso é porque, bem, é bastante complicado. No entanto, tenho visto desenvolvedores fazerem coisas muito mais complicadas e de domínio específico para evitar lidar com esse this
. Se você não tiver certeza sobre this
, espero que isto ajude. Este é o meu guia sobre this
.
Vou começar com a situação mais específica e terminar com a menos específica. Este artigo é meio 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 de,
this
'vinculado' - Caso contrário, se
this
for definido no tempo da chamada - Caso contrário, se a função for chamada através de um objeto pai (
parent.func()
) - Caso contrário, se a função ou escopo pai estiver no modo strict
- Caso contrário
Se a função for definida como uma função de seta: #
const arrowFunction = () => {
console.log(this);
};
Neste caso, o valor de this
é sempre o mesmo que this
no escopo do bloco 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 e ele é sempre igual ao valor do 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 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 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 'vinculados' #
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 ouvintes de eventos em componentes (como componentes React ou componentes web).
O código acima pode parecer que está quebrando a regra "O this
será igual ao this
do escopo pai" mas ele começa a fazer sentido se você pensar nos campos de classe como uma outra maneira de 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 dentro do construtor. Se você não pode usar campos de classe por algum motivo, atribuir funções dentro do 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 irá chamar Whatever
(ou seu construtor, se for uma classe) com this
definido com 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 é verdadeiro para construtores 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 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 de, this
'vinculado': #
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Sempre que boundFunction
for chamada, o valor de this
será o objeto transmitido a bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Outros exemplos #
Ao chamar uma função vinculada (bound), 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 (bound), 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 tempo 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 passado para call
/apply
.
Infelizmente, this
é definido com algum outro valor por coisas como ouvintes de eventos DOM. Nesses casos, usá-lo pode resultar em código difícil de entender:
Não faça
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 faço:
Faça
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 através de um objeto pai (parent.func()
): #
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
Neste caso, a função é chamada como um membro de obj
, então this
será obj
. Isto acontece no momento da chamada, então o link é quebrado se a função for chamada sem seu 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 o método someMethod
não é chamado como membro de obj
. Você poderá encontrar este problema se tentar algo assim:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Isto quebra porque a implementação de querySelector
olha para o seu próprio valor de this
e espera que seja algum tipo de nó do DOM e isto quebra a ligação. Para alcançar o resultado desejado acima da forma correta, use:
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 alterados para evitar referências this
, portanto, o log
não precisa ser vinculado ao console
.
Caso contrário, se a função ou escopo pai estiver no modo strict: #
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
Neste caso, o valor de this
é undefined. 'use strict'
não é necessário na função se o escopo pai já estiver no modo strict (e todos os módulos estiverem no modo strict).
Caso contrário: #
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
Nesse caso, o valor de this
é o mesmo que o de globalThis
.
Ufa! #
E é isso! Isso é tudo que sei sobre this
. Alguma pergunta? Algo que perdi? Sinta-se à vontade para twittar para mim.
Agradecimentos a Mathias Bynens , Ingvar Stepanyan e Thomas Steiner pela revisão.