this e o mundo dos métodos
Toda função tradicional carrega uma palavra traiçoeira chamada this, cujo valor muda dependendo de COMO a função é chamada, não de onde ela foi escrita. Ignorar essa diferença é fonte de um dos bugs mais confusos do JavaScript.
Estudar esta lição interativa →Ao criar um objeto com um método, é natural esperar que aquele método consiga acessar os outros dados do próprio objeto. E, quando você chama o método do jeito normal, isso funciona: dentro dele, a palavra especial this aponta para o objeto que fez a chamada. O problema aparece quando esse método é emprestado para outro lugar, por exemplo, entregue como callback para um setTimeout ou um addEventListener. Nesses casos, é comum o this deixar de apontar para o objeto: ele passa a ser o objeto global da página (window) em código comum, ou undefined em modo estrito, e os campos do objeto "somem". O código quebra de um jeito que confunde até quem já programa há um tempo.
// Chamado como método (jogador.mostrarPontos()), this = jogador. Funciona:
const jogador = {
nome: "Ana",
pontos: 10,
mostrarPontos: function () {
console.log(this.nome + " tem " + this.pontos + " pontos");
}
};
jogador.mostrarPontos(); // Ana tem 10 pontos
// Mas se a função "solta" perde o dono, this se perde:
const funcaoSolta = jogador.mostrarPontos;
funcaoSolta(); // undefined tem undefined pontos — this não é mais o jogador (virou o objeto global; em modo estrito daria TypeError)
// bind devolve uma cópia da função com o this preso ao objeto, para sempre:
const presa = jogador.mostrarPontos.bind(jogador);
presa(); // Ana tem 10 pontosResumindo os casos que você vai encontrar. (1) objeto.metodo(), com o objeto na frente do ponto: this é esse objeto. (2) A mesma função chamada solta, como um callback: em código comum o this vira o objeto global (window), por isso os campos aparecem como undefined; em modo estrito (módulos e classes) ele vira undefined e this.nome lança um TypeError. (3) Uma function tradicional passada para addEventListener: o this é o próprio elemento que recebeu o evento. E existe um jeito de fixar o dono: .bind(objeto) devolve uma cópia da função com o this preso àquele objeto, como no fim do exemplo acima.
Pense no this como um crachá que a função recebe emprestado no momento em que é chamada, não em que foi escrita. Quem empresta o crachá é justamente quem faz a chamada: chamada como jogador.mostrarPontos(), o crachá diz "jogador", e this vira o objeto jogador. Só que, se você entrega a função solta (sem o jogador. na frente, como acontece quando ela vira um callback), ninguém empresta o crachá certo, e o this lá dentro fica sem o dono que você esperava.
É aqui que as arrow functions do Módulo 5 resolvem um problema que você ainda nem sabia que tinha. Diferente das funções tradicionais, uma arrow function não tem o seu próprio this: ela pega emprestado o this do lugar onde foi escrita (o escopo ao redor), e nunca o perde, não importa como for chamada depois. Por isso, dentro de um método, uma arrow function usada como callback continua enxergando o this do objeto corretamente:
// Arrow function NÃO tem this próprio: herda o this de onde foi ESCRITA.
const jogador = {
nome: "Ana",
pontos: 10,
comemorar: function () {
// Esta arrow function foi escrita dentro de "comemorar", então herda
// o this de "comemorar" (o próprio jogador), mesmo rodando depois, no timer:
setTimeout(() => {
console.log(this.nome + " comemora " + this.pontos + " pontos!");
}, 10);
}
};
jogador.comemorar(); // Ana comemora 10 pontos! (this preservado)Dentro de um método de objeto, se você precisa passar uma função como callback (para um setTimeout, um addEventListener, um map) e essa função precisa continuar enxergando o this do objeto, use uma arrow function. Se a função é o próprio método, chamado diretamente como objeto.metodo(), uma function tradicional funciona bem, e é inclusive a forma mais comum de declarar métodos, como você vai ver logo no próximo módulo, quando entrarem as classes.
Esse detalhe aparece toda hora em produto real: um placar de jogo que atualiza a tela depois de um pequeno atraso, um componente que reage a um clique lembrando de seus próprios dados, um temporizador de sessão que precisa saber a que usuário pertence. Em todos, o mesmo raciocínio: "esta função vai ser chamada de um jeito que perde o dono? Se sim, arrow function."
// Sua vez! Troque a function tradicional dentro do setTimeout por uma arrow function.
const jogo = {
tempo: 3,
comecarContagem: function () {
const tick = function () {
this.tempo--;
console.log("tempo:", this.tempo);
if (this.tempo > 0) setTimeout(tick, 5);
};
setTimeout(tick, 5);
}
};
jogo.comecarContagem();Troque a função interna por uma arrow function e clique em Rodar.