programandoai

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 pontos

Resumindo 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)
Regra prática (sem entrar em classes ainda)

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.

← Voltar ao índice do curso