O this em profundidade
Nesta aula, exploramos o comportamento da palavra-chave `this` em JavaScript, cobrindo as regras de ligação, os métodos `call`, `apply` e `bind`, e as diferenças entre arrow functions e funções normais. Com exemplos práticos e exercícios, você aprenderá a prever e controlar o valor de `this` em diferentes contextos.
Em JavaScript, a palavra-chave this é uma das fontes mais comuns de confusão, mas também uma das mais poderosas. Diferente de outras linguagens, o valor de this não é definido pelo escopo léxico, mas sim pela forma como a função é chamada. Compreender as regras de ligação de this é essencial para escrever código previsível e evitar bugs sutis. Nesta aula, vamos mergulhar nas quatro regras de ligação, explorar os métodos call, apply e bind, e entender como as arrow functions mudam completamente o jogo.
Antes de continuar, é importante destacar que this é avaliado em tempo de execução, não em tempo de escrita. Isso significa que a mesma função pode ter valores diferentes de this dependendo de onde e como é invocada. Vamos desvendar esses mistérios com exemplos práticos e exercícios que reforçam cada conceito.
Regras de ligação
O valor de this é determinado por quatro regras, em ordem de precedência: ligação implícita, ligação explícita, ligação new e ligação padrão. Vamos examinar cada uma.
1. Ligação implícita: Quando uma função é chamada como método de um objeto, o this dentro da função refere-se a esse objeto. Por exemplo:
const pessoa = {
nome: 'João',
saudacao() {
console.log(`Olá, eu sou ${this.nome}`);
}
};
pessoa.saudacao(); // Olá, eu sou João2. Ligação explícita: Usando call, apply ou bind, podemos definir explicitamente o valor de this (veremos detalhes na próxima seção).
3. Ligação new: Quando usamos o operador new para criar um objeto a partir de uma função construtora, o this dentro da função aponta para o novo objeto criado.
function Carro(marca) {
this.marca = marca;
}
const meuCarro = new Carro('Fiat');
console.log(meuCarro.marca); // Fiat4. Ligação padrão: Se nenhuma das regras anteriores se aplicar, o this assume o valor global (em modo estrito, undefined). Em navegadores, o objeto global é window; no Node.js, é global.
function mostrarThis() {
console.log(this);
}
mostrarThis(); // window (ou undefined em strict mode)A ordem de precedência é: new > ligação explícita > ligação implícita > ligação padrão. Vamos ver um exemplo que combina várias regras:
const obj = {
valor: 42,
funcao() {
console.log(this.valor);
}
};
obj.funcao.call({ valor: 99 }); // 99 (ligação explícita vence a implícita)
new obj.funcao(); // undefined (this é um novo objeto vazio)call, apply e bind
Os métodos call, apply e bind são usados para controlar explicitamente o valor de this em uma função. Eles são propriedades de todas as funções JavaScript.
call e apply invocam a função imediatamente, com um this especificado e argumentos. A diferença está na forma de passar os argumentos: call recebe uma lista de argumentos separados por vírgula, enquanto apply recebe um array de argumentos.
function apresentar(prefixo, sufixo) {
console.log(`${prefixo} ${this.nome} ${sufixo}`);
}
const pessoa = { nome: 'Maria' };
apresentar.call(pessoa, 'Olá', '!'); // Olá Maria !
apresentar.apply(pessoa, ['Oi', '!!']); // Oi Maria !!
bind não invoca a função imediatamente; em vez disso, retorna uma nova função com o this fixado. Isso é útil para criar funções com contexto pré-definido, especialmente em callbacks ou event handlers.
const pessoa = {
nome: 'Carlos',
saudacao: function() {
console.log(`Olá, meu nome é ${this.nome}`);
}
};
const saudacao = pessoa.saudacao.bind(pessoa);
saudacao(); // Olá, meu nome é Carlos
Além de fixar this, bind também pode pré-definir argumentos, criando funções parciais:
function multiplicar(a, b) {
return a * b;
}
const dobrar = multiplicar.bind(null, 2);
console.log(dobrar(5)); // 10
this em arrow vs função normal
As arrow functions não têm seu próprio this. Em vez disso, elas herdam o this do escopo léxico (o escopo onde foram definidas). Isso é uma diferença crucial em relação às funções normais, que recebem o this conforme a chamada.
Considere o exemplo clássico de um temporizador:
function Contador() {
this.valor = 0;
setInterval(function() {
this.valor++; // this é o objeto global, não Contador
console.log(this.valor); // NaN
}, 1000);
}
new Contador();
Com uma arrow function, o this é capturado do escopo da função Contador, que é a instância criada:
function Contador() {
this.valor = 0;
setInterval(() => {
this.valor++; // this é a instância de Contador
console.log(this.valor); // 1, 2, 3...
}, 1000);
}
new Contador();
Outra diferença: arrow functions não podem ser usadas como construtoras (com new) e não têm a propriedade prototype. Além disso, os métodos call, apply e bind não podem alterar o this de uma arrow function, pois ela ignora qualquer valor explicitamente definido.
const arrow = () => console.log(this);
arrow.call({ nome: 'Teste' }); // this continua sendo o escopo global (ou undefined em strict)
Portanto, a escolha entre arrow e função normal depende do contexto: use arrow functions quando precisar do this do escopo externo, e funções normais quando precisar de um this dinâmico (como em métodos de objeto).
Boas práticas e observações finais
- Use
bindao passar métodos como callbacks para garantir que othisseja o esperado. - Prefira arrow functions em callbacks que dependem do contexto externo, como em
setTimeoutou event listeners, para evitar o problema dothisperdido. - Em classes, os métodos são ligados à instância automaticamente, mas cuidado ao extraí-los; use
bindou arrow functions como propriedades de classe (em Babel/TypeScript) para manter othiscorreto. - Em módulos ES6, o escopo global não é
window; em navegadores, ainda é, mas em Node.js églobal. Em código estrito, othisglobal éundefinedem funções simples. - Evite usar
thisem funções que serão chamadas de maneiras diferentes; prefira parâmetros explícitos quando possível.
Referências
- MDN: this
- MDN: Function.prototype.call
- MDN: Function.prototype.apply
- MDN: Function.prototype.bind
- MDN: Arrow functions
- javascript.info: The 'this' keyword
- W3Schools: JavaScript this
Exercícios
- Explique qual será o valor de
thisem cada chamada e por quê:const obj = { nome: 'objeto', metodo() { return this.nome; } }; const fn = obj.metodo; console.log(obj.metodo()); console.log(fn());✓ Resposta:obj.metodo()retorna 'objeto' (ligação implícita, pois é chamada como método).fn()retornaundefined(ou erro em strict mode) porque a função é chamada sem contexto, entãothisé o objeto global (ou undefined), ethis.nomeé indefinido. - Escreva uma função que aceite um número e use
callpara passar um objeto com uma propriedademultiplicadore retorne o produto. - Crie uma função que use
bindpara fixar othisa um objeto e depois chame a função resultante para exibir uma mensagem. - Converta a seguinte função para usar uma arrow function e explique o que acontece com o
this:function Timer() { this.segundos = 0; setInterval(function() { this.segundos++; console.log(this.segundos); }, 1000); } new Timer(); - Explique por que o código a seguir imprime
undefinede como corrigi-lo usandobind:const carro = { marca: 'Toyota', exibirMarca: function() { console.log(this.marca); } }; setTimeout(carro.exibirMarca, 1000);
function calcularMultiplo(num) {
return num * this.multiplicador;
}
const contexto = { multiplicador: 3 };
const resultado = calcularMultiplo.call(contexto, 5);
console.log(resultado); // 15const usuario = { nome: 'Ana', saudacao: function() { console.log('Olá, ' + this.nome); } };
const saudar = usuario.saudacao.bind(usuario);
saudar(); // Olá, Anafunction Timer() {
this.segundos = 0;
setInterval(() => {
this.segundos++;
console.log(this.segundos);
}, 1000);
}
new Timer(); A arrow function captura o this do escopo de Timer, que é a instância criada, então this.segundos incrementa corretamente.this dentro de exibirMarca não é o objeto carro quando passado como callback, porque o setTimeout chama a função sem contexto, fazendo o this ser o objeto global (ou undefined). A correção: setTimeout(carro.exibirMarca.bind(carro), 1000); ou usar arrow function: setTimeout(() => carro.exibirMarca(), 1000);