O this em profundidade
Esta aula explora o comportamento do operador `this` em JavaScript, cobrindo as regras de ligação (padrão, implícita, explícita e `new`), os métodos `call`, `apply` e `bind`, e as diferenças cruciais entre funções normais e arrow functions. Ao final, você será capaz de prever o valor de `this` em qualquer contexto e aplicará boas práticas para evitar bugs comuns.
O operador this é um dos conceitos mais confusos e poderosos do JavaScript. Ele permite que funções acessem o contexto de execução, mas seu valor é determinado dinamicamente, dependendo de como a função é chamada. Nesta aula, vamos desmistificar o this em profundidade, explorando as regras de ligação, os métodos que permitem controlar explicitamente seu valor e as diferenças entre funções normais e arrow functions. Com exemplos práticos e exercícios, você ganhará confiança para usar this de forma intencional e evitar erros sutis.
Entender o this é essencial para dominar JavaScript, especialmente em programação orientada a objetos, manipulação de eventos e frameworks como React. Vamos começar com as regras fundamentais que determinam o valor de this em diferentes situações.
Regras de ligação
O valor de this não é definido no momento em que a função é escrita, mas sim quando ela é chamada. Existem quatro regras principais que determinam esse valor: ligação padrão, ligação implícita, ligação explícita e ligação com new. Vamos analisar cada uma.
Ligação padrão: quando uma função é chamada sem nenhum contexto (ou seja, como uma função simples), this aponta para o objeto global (em navegadores, window; em Node.js, global). Em modo estrito ('use strict'), this é undefined nesse caso, o que evita poluição do escopo global.
function mostrarThis() {
console.log(this); // window (ou undefined em strict mode)
}
mostrarThis(); // chamada simples
Ligação implícita: quando a função é chamada como método de um objeto, o this se refere ao objeto que contém o método. Isso é o caso mais comum.
const pessoa = {
nome: 'João',
saudacao: function() {
console.log(`Olá, meu nome é ${this.nome}`);
}
};
pessoa.saudacao(); // Olá, meu nome é João
Ligação explícita: podemos forçar o valor de this usando os métodos call, apply e bind, que veremos em detalhes na próxima seção.
Ligação com new: quando usamos uma função como construtora com o operador new, o this se refere ao novo objeto criado.
function Carro(modelo) {
this.modelo = modelo;
}
const meuCarro = new Carro('Fusca');
console.log(meuCarro.modelo); // Fusca
Essas regras têm uma ordem de precedência: new > ligação explícita > ligação implícita > ligação padrão. Isso significa que, se várias regras se aplicarem, a de maior precedência vence. Por exemplo, se uma função é chamada com new e também usa call, o new prevalece (embora na prática isso não seja comum).
call, apply e bind
Os métodos call, apply e bind são nativos de todas as funções e permitem controlar explicitamente o valor de this. Eles são fundamentais para reutilizar funções em diferentes contextos.
call e apply invocam a função imediatamente, com o primeiro argumento sendo o valor de this. A diferença está na passagem dos argumentos: call recebe argumentos separados por vírgula, enquanto apply recebe um array (ou objeto array-like).
function apresentar(cidade, pais) {
console.log(`${this.nome} mora em ${cidade}, ${pais}`);
}
const pessoa = { nome: 'Maria' };
apresentar.call(pessoa, 'São Paulo', 'Brasil');
// Maria mora em São Paulo, Brasil
apresentar.apply(pessoa, ['Rio de Janeiro', 'Brasil']);
// Maria mora em Rio de Janeiro, Brasil
Já bind não executa a função; em vez disso, retorna uma nova função com o this permanentemente associado ao valor passado. Isso é útil para criar funções com contexto fixo, como em callbacks de eventos.
const pessoa = {
nome: 'Ana',
dizerOi: function() {
console.log(`Oi, eu sou ${this.nome}`);
}
};
const dizerOiDesacoplado = pessoa.dizerOi.bind(pessoa);
dizerOiDesacoplado(); // Oi, eu sou Ana
Um caso clássico é usar bind para evitar perder o contexto em callbacks, como em setTimeout ou eventos de DOM. Sem bind, o this seria o objeto global (ou undefined em strict mode).
this em arrow vs função normal
As arrow functions, introduzidas no ES6, têm um comportamento radicalmente diferente em relação ao this. Elas não possuem seu próprio this; em vez disso, elas capturam o this do escopo léxico (ou seja, o escopo onde foram definidas). Isso torna o this previsível e evita muitos problemas comuns.
Em funções normais, o this é dinâmico e pode mudar dependendo da chamada. Já em arrow functions, o this é fixo no momento da criação e não pode ser alterado com call, apply ou bind (esses métodos são ignorados para o this). Vamos ver um exemplo:
const objeto = {
nome: 'Objeto',
funcaoNormal: function() {
console.log(this); // objeto
},
arrow: () => {
console.log(this); // escopo externo (provavelmente window)
}
};
objeto.funcaoNormal(); // objeto
objeto.arrow(); // window (ou o escopo onde objeto foi definido)
No exemplo acima, a arrow function não cria seu próprio this; ela usa o this do escopo onde foi definida. Se o objeto foi definido no escopo global, this será o objeto global. Isso é útil para usar this dentro de callbacks, como em setTimeout ou em métodos de array, onde queremos manter o contexto externo.
const contador = {
valor: 0,
incrementar: function() {
setTimeout(() => {
this.valor++;
console.log(this.valor);
}, 100);
}
};
contador.incrementar(); // 1
Se usássemos uma função normal no setTimeout, o this seria o objeto global, e o código falharia. As arrow functions são, portanto, ideais para funções que precisam acessar o contexto do escopo pai.
No entanto, arrow functions não devem ser usadas como métodos de objeto quando se espera que o this se refira ao objeto, pois elas não têm seu próprio this. Elas também não podem ser usadas como construtoras (com new) e não têm a propriedade prototype.
Boas práticas e observações finais
Ao trabalhar com this, é importante ter clareza sobre o contexto de cada função. Algumas boas práticas incluem: usar arrow functions para callbacks que precisam do contexto externo, evitar usar this em funções soltas (prefira parâmetros ou variáveis), e usar bind para fixar o contexto quando necessário. Além disso, o modo estrito ('use strict') ajuda a evitar erros, tornando this undefined em chamadas padrão.
Outra dica é nomear funções que usam this de forma descritiva, para que outros desenvolvedores (e você mesmo) entendam o contexto esperado. E lembre-se: em arrow functions, o this é lexical, então não tente alterá-lo com call ou bind — isso não funcionará.
Referências
- MDN: Operador this
- MDN: Function.prototype.call()
- MDN: Function.prototype.apply()
- MDN: Function.prototype.bind()
- MDN: Arrow functions
- javascript.info: The JavaScript `this`
- W3Schools: JavaScript this
Exercícios
Explique qual será o valor de
thisna chamada abaixo e justifique com base nas regras de ligação:function teste() { console.log(this); } teste();✓ Resposta: Em uma chamada de função simples (sem contexto), othisserá o objeto global (windowno navegador,globalno Node.js), a menos que o modo estrito esteja ativo, caso em que seráundefined. Isso é a ligação padrão.Crie um objeto
calculadoracom um métodosomarque recebe dois números e retorna a soma, usandothispara acessar uma propriedadeprecisao(número de casas decimais). Depois, usecallpara invocar o método com outro objeto que tenha um valor diferente deprecisao.✓ Resposta:const calculadora = { precisao: 2, somar: function(a, b) { const resultado = (a + b).toFixed(this.precisao); return Number(resultado); } }; const outraCalculadora = { precisao: 4 }; console.log(calculadora.somar.call(outraCalculadora, 1.2345, 2.3456)); // 3.5801Diferencie
call,applyebindcom exemplos práticos.✓ Resposta:calleapplyinvocam a função imediatamente, mascallrecebe argumentos separados por vírgula, enquantoapplyrecebe um array.bindretorna uma nova função com othisfixo, sem executá-la. Exemplo:function saudacao(mensagem, pontuacao) { console.log(`${mensagem}, ${this.nome}${pontuacao}`); } const pessoa = { nome: 'Carlos' }; saudacao.call(pessoa, 'Olá', '!'); // Olá, Carlos! saudacao.apply(pessoa, ['Oi', '?']); // Oi, Carlos? const saudacaoFixada = saudacao.bind(pessoa, 'Bem-vindo'); saudacaoFixada('.'); // Bem-vindo, Carlos.Explique por que o código abaixo não funciona como esperado e como você o corrigiria usando uma arrow function:
const pessoa = { nome: 'Maria', apresentar: function() { setTimeout(function() { console.log(`Olá, eu sou ${this.nome}`); }, 100); } }; pessoa.apresentar();✓ Resposta: Dentro dosetTimeout, a função normal tem seu própriothisque, em chamada simples, aponta para o objeto global (ouundefinedem strict mode). Para corrigir, use uma arrow function, que captura othisdo escopo externo (o objeto pessoa):setTimeout(() => { console.log(`Olá, eu sou ${this.nome}`); }, 100);Escreva uma função construtora
Pessoaque recebanomeeidade, e um métodoapresentarque use arrow function para manter o contexto. Crie uma instância e chame o método.✓ Resposta:function Pessoa(nome, idade) { this.nome = nome; this.idade = idade; this.apresentar = () => { console.log(`Meu nome é ${this.nome} e tenho ${this.idade} anos.`); }; } const joao = new Pessoa('João', 30); joao.apresentar(); // Meu nome é João e tenho 30 anos.