this em JavaScript

TL;DR

this é uma referência ao contexto de execução de uma função — e o seu valor é determinado por como a função é chamada, não por onde ela foi definida. Existem quatro regras de binding em ordem de precedência: new > explícito (call/apply/bind) > implícito (método) > default (global ou undefined em strict mode). Arrow functions não têm this próprio: elas herdam o this do escopo léxico onde foram criadas. A maior fonte de bugs é “perda de this”: passar um método como callback faz com que ele perca o vínculo com o objeto original.


Imagine que você está num restaurante. O garçom trabalha no restaurante inteiro — ele é o mesmo “objeto” independente de onde estiver — mas o contexto do que ele faz muda: quando está no salão, “o cliente” significa quem está sentado à mesa; quando está na cozinha, “o cliente” é o chef que pediu um prato. O garçom não mudou. O contexto de quem é “o cliente” mudou.

this em JavaScript funciona assim. A palavra this dentro de uma função é sempre uma referência ao contexto de execução atual, e esse contexto muda dependendo de como a função foi chamada.

Essa distinção — “como chamada” vs. “onde definida” — é a raiz de boa parte dos erros mais clássicos em entrevistas e em produção.


O problema central: this não é quem você pensa

const pessoa = {
  nome: "Alice",
  saudar: function () {
    console.log("Olá, eu sou " + this.nome);
  },
};
 
pessoa.saudar(); // "Olá, eu sou Alice"
 
const fn = pessoa.saudar;
fn(); // "Olá, eu sou undefined"  ← por quê?

pessoa.saudar e fn apontam para a mesma função. Mas o resultado é diferente porque o contexto de chamada mudou. Na segunda linha, fn() é uma chamada direta — sem objeto na frente — e por isso this perdeu o vínculo com pessoa.

Para entender por que isso acontece, precisamos conhecer as quatro regras de binding.


As quatro regras de binding

Regra 1 — Default binding (binding padrão)

Quando uma função é chamada de forma simples, sem nenhum objeto à esquerda do ponto, sem new, sem call/apply/bind, aplica-se o binding padrão:

  • Fora de strict mode: this aponta para o objeto global (window no browser, globalThis no Node).
  • Em strict mode ("use strict"): this é undefined.
function quemSouEu() {
  console.log(this);
}
 
quemSouEu(); // window (browser) ou globalThis (Node) — fora de strict mode
 
"use strict";
function quemSouEuStrict() {
  console.log(this);
}
 
quemSouEuStrict(); // undefined

Por que strict mode muda isso?

Em JavaScript pré-strict, o motor “embrulhava” this no objeto global quando nenhum contexto era fornecido. O strict mode aboliu esse embrulho automático porque ele causava bugs silenciosos: ao invés de um erro claro, o código mutava variáveis globais sem querer.


Regra 2 — Implicit binding (binding implícito / método)

Quando a função é chamada como método de um objeto — ou seja, com um objeto à esquerda do ponto — this aponta para aquele objeto.

const gato = {
  nome: "Whiskers",
  miar: function () {
    console.log(this.nome + ": miau!");
  },
};
 
gato.miar(); // "Whiskers: miau!"
// this = gato, porque gato está à esquerda do ponto

A regra é simples: olhe para o objeto imediatamente à esquerda do ponto no momento da chamada.

const cachorro = { nome: "Rex" };
cachorro.miar = gato.miar; // mesmo método, objeto diferente
 
cachorro.miar(); // "Rex: miau!"
// this = cachorro agora

O método viajou de um objeto para outro — e this viajou junto, porque o que importa é quem está à esquerda do ponto no momento da chamada.


Regra 3 — Explicit binding (call, apply, bind)

Às vezes você quer chamar uma função com um contexto específico, independente de como ela seria chamada normalmente. Para isso existem três métodos nativos de Function:

call(thisArg, arg1, arg2, ...)

Chama a função imediatamente, com this = thisArg. Argumentos passados individualmente.

function apresentar(profissao) {
  console.log(`${this.nome} é ${profissao}`);
}
 
const joao = { nome: "João" };
apresentar.call(joao, "desenvolvedor"); // "João é desenvolvedor"

apply(thisArg, [args])

Igual ao call, mas argumentos passados como array. Útil quando você já tem os argumentos numa lista.

apresentar.apply(joao, ["designer"]); // "João é designer"

bind(thisArg, ...args) — cria uma função nova

bind não chama a função imediatamente. Ele retorna uma nova função com this permanentemente fixado. Argumentos pré-fixados são opcionais (currying parcial).

const apresentarJoao = apresentar.bind(joao);
apresentarJoao("gerente"); // "João é gerente"
// this sempre será joao, não importa como apresentarJoao for chamada

Regra 4 — new binding

Quando você chama uma função com new, o JavaScript executa quatro passos internamente:

  1. Cria um objeto vazio {}.
  2. Define o protótipo desse objeto.
  3. Executa a função com this apontando para esse objeto novo.
  4. Retorna o objeto (a menos que a função retorne explicitamente outro objeto).
function Carro(modelo) {
  this.modelo = modelo; // this = o objeto recém-criado
  this.ligar = function () {
    console.log(this.modelo + " ligado.");
  };
}
 
const fusca = new Carro("Fusca");
fusca.ligar(); // "Fusca ligado."

new tem a maior precedência de todas as regras: mesmo que você tente usar call/apply/bind junto com new, o new vence.


Tabela de precedência

                      ┌─────────────────────────────────────────────┐
                      │       ORDEM DE PRECEDÊNCIA DO this          │
                      └─────────────────────────────────────────────┘

  MAIOR ──────────────────────────────────────────────────────► MENOR

  new binding   >  explicit binding  >  implicit binding  >  default
  (new Fn())       (call/apply/bind)    (obj.método())       (fn())

Quando você se perguntar “o que é this aqui?”, percorra esta lista de cima para baixo e use a primeira regra que se aplicar.


flowchart TD
    A[Função chamada] --> B{Com new?}
    B -->|Sim| C["this = objeto novo criado\n(new binding)"]:::ok
    B -->|Não| D{Com call/apply/bind?}
    D -->|Sim| E["this = argumento passado\n(explicit binding)"]:::ok
    D -->|Não| F{Como método de objeto?}
    F -->|Sim| G["this = objeto à esquerda do ponto\n(implicit binding)"]:::ok
    F -->|Não| H{Strict mode?}
    H -->|Sim| I["this = undefined\n(default strict)"]:::warn
    H -->|Não| J["this = objeto global\n(default sloppy)"]:::warn

    classDef ok fill:#4A90D9,color:#fff,stroke:none
    classDef warn fill:#F5A623,color:#fff,stroke:none

Arrow functions: this léxico

Arrow functions são a exceção a todas as quatro regras acima. Elas não têm this próprio. Quando você escreve () => {}, o motor não cria um binding de this para essa função — ele simplesmente captura o this do escopo léxico onde a arrow function foi definida.

const obj = {
  nome: "Bob",
  saudar: function () {
    // Esta função tem seu próprio this (= obj, pelo binding implícito)
 
    const arrow = () => {
      // Esta arrow não tem this próprio.
      // Ela usa o this do escopo onde foi definida: a função saudar
      console.log(this.nome); // "Bob"
    };
 
    arrow();
  },
};
 
obj.saudar(); // "Bob"

Compare com uma função regular no mesmo lugar:

const obj2 = {
  nome: "Carol",
  saudar: function () {
    function regular() {
      console.log(this.nome); // undefined (ou erro em strict mode)
    }
    regular(); // chamada sem objeto → default binding
  },
};
 
obj2.saudar(); // undefined

Arrow functions e call/apply/bind

Você não pode mudar o this de uma arrow function com call, apply ou bind. Esses métodos são ignorados para arrows — o this léxico permanece fixo.

const arrow = () => console.log(this);
arrow.call({ x: 42 }); // ainda imprime o this léxico, ignora o argumento

this no nível de módulo ESM é sempre undefined

Em módulos ESM (arquivos .mjs ou <script type="module">), o this no topo do arquivo é sempre undefined — não window, não globalThis. Isso surpreende quem migra código de <script> comum para módulo.

// script comum (sloppy mode)
console.log(this); // window
 
// módulo ESM (strict mode implícito)
console.log(this); // undefined

O motivo: módulos ESM executam em strict mode por padrão — e no nível superior de um módulo não há objeto de chamada, então o default binding retorna undefined. Arrow functions no topo de um módulo capturam esse undefined léxico. Se você precisa do objeto global, use globalThis explicitamente.


this em classes

Classes JavaScript são "use strict" por padrão — o corpo inteiro de uma classe executa em strict mode. Isso tem implicações diretas para this:

class Contador {
  constructor() {
    this.valor = 0;
  }
 
  incrementar() {
    this.valor++;
    console.log(this.valor);
  }
}
 
const c = new Contador();
c.incrementar(); // 1 — this = c (implicit binding)
 
const inc = c.incrementar;
inc(); // TypeError: Cannot read properties of undefined (reading 'valor')
// this é undefined em strict mode!

A mesma perda de this do exemplo inicial — agora com um erro explícito porque classes são strict por padrão.

Soluções para métodos de classe

1. Bind no construtor (padrão clássico React):

class Contador {
  constructor() {
    this.valor = 0;
    this.incrementar = this.incrementar.bind(this); // fixa o this
  }
 
  incrementar() {
    this.valor++;
  }
}

2. Class field com arrow function (padrão moderno):

class Contador {
  valor = 0;
 
  incrementar = () => {
    // Arrow: herda this léxico do construtor
    this.valor++;
  };
}
 
const c = new Contador();
const inc = c.incrementar;
inc(); // funciona! this ainda é c

Casos práticos

Cenário 1: handler de evento perdendo this

Um erro clássico ao usar addEventListener com métodos de objeto:

class Botao {
  constructor(label) {
    this.label = label;
    this.elemento = document.createElement("button");
 
    // ❌ ERRADO: passa a referência da função sem o contexto
    this.elemento.addEventListener("click", this.handleClick);
  }
 
  handleClick() {
    // Quando o evento dispara, this = elemento do DOM (ou undefined em strict)
    console.log("Clicou em: " + this.label); // undefined!
  }
}
class Botao {
  constructor(label) {
    this.label = label;
    this.elemento = document.createElement("button");
 
    // ✅ CORRETO: arrow function captura o this léxico (a instância)
    this.elemento.addEventListener("click", () => this.handleClick());
 
    // ✅ TAMBÉM CORRETO: bind explícito
    this.elemento.addEventListener("click", this.handleClick.bind(this));
  }
 
  handleClick() {
    console.log("Clicou em: " + this.label); // funciona!
  }
}

Cenário 2: método passado como callback para array/timer

const relogio = {
  hora: 0,
  tick: function () {
    console.log("Hora atual:", this.hora);
  },
};
 
// ❌ ERRADO: setInterval chama tick sem objeto na frente → default binding
setInterval(relogio.tick, 1000); // "Hora atual: undefined"
 
// ✅ CORRETO com arrow:
setInterval(() => relogio.tick(), 1000); // "Hora atual: 0"
 
// ✅ CORRETO com bind:
setInterval(relogio.tick.bind(relogio), 1000); // "Hora atual: 0"

O mesmo problema ocorre com Array.prototype.forEach, map, filter e qualquer função de alta ordem que receba um callback:

const listagem = {
  prefixo: "item",
  itens: [1, 2, 3],
  imprimir: function () {
    // ❌ function regular: this dentro do forEach = undefined (strict) ou global
    this.itens.forEach(function (item) {
      console.log(this.prefixo + item); // TypeError ou "undefinedX"
    });
 
    // ✅ arrow: herda this do método imprimir (= o objeto listagem)
    this.itens.forEach((item) => {
      console.log(this.prefixo + item); // "item1", "item2", "item3"
    });
  },
};
 
listagem.imprimir();

Cenário 3: desestruturação quebra o método

const usuario = {
  nome: "Diana",
  cumprimentar() {
    return `Olá, ${this.nome}!`;
  },
};
 
// ❌ Desestruturar o método arranca a função do objeto
const { cumprimentar } = usuario;
cumprimentar(); // "Olá, undefined!" (ou TypeError em strict)
 
// ✅ Solução: chamar sempre com o objeto
usuario.cumprimentar(); // "Olá, Diana!"
 
// ✅ Ou: bind antes de desestruturar
const cumprimentar2 = usuario.cumprimentar.bind(usuario);
cumprimentar2(); // "Olá, Diana!"

this em uma frase

this é um parâmetro implícito que toda função recebe no momento da chamada, e seu valor é determinado por quem chamou a função — não por quem a escreveu.


Armadilhas comuns

Perda de this ao passar método como argumento

O que acontece: obj.metodo passado como callback imprime undefined ou acessa o global. Por quê: Ao separar a função do objeto (const fn = obj.metodo), você perde o vínculo. A função existe, mas o contexto desapareceu. Como evitar: Use () => obj.metodo() (arrow wrapper) ou obj.metodo.bind(obj) para preservar o contexto.

Arrow function como método de objeto não funciona como esperado

O que acontece: Você define { saudar: () => this.nome } esperando que this seja o objeto — mas não é. Por quê: Arrow captura o this léxico do escopo onde foi criada. Se o objeto foi criado no escopo global, this dentro da arrow é window/undefined, não o objeto. Como evitar: Métodos de objeto devem ser funções normais (function) ou métodos de classe. Reserve arrow functions para callbacks dentro desses métodos.

this em setTimeout e setInterval é o global

O que acontece: Código dentro de setTimeout(function() { this.x }, ms) acessa this global. Por quê: setTimeout chama o callback como função standalone — default binding. Como evitar: Use arrow function: setTimeout(() => { this.x }, ms) captura o this léxico correto.

Classes são strict por padrão — erros aparecem mais cedo

O que acontece: Método de classe chamado sem objeto lança TypeError em vez de acessar silenciosamente o global. Por quê: O corpo de uma classe executa em strict mode automaticamente, onde this sem binding é undefined. Como evitar: Sempre chame métodos via instância, ou use bind / arrow fields no construtor.


Como explicar em inglês

this refers to the execution context of a function — it’s determined by how the function is called, not where it was defined. There are four binding rules in order of precedence: new, explicit (call/apply/bind), implicit (method call), and default (standalone call). Arrow functions don’t have their own this; they inherit it lexically from the surrounding scope.

PTEN
thisthis / execution context
binding de thisthis binding
binding padrãodefault binding
binding implícitoimplicit binding
binding explícitoexplicit binding
binding por newnew binding
perda de thislosing this / this detachment
escopo léxicolexical scope
função flechaarrow function
modo estritostrict mode
vincularbind

O que vem a seguir

Agora que você entende como this se comporta, o próximo território natural é entender closures: funções que capturam variáveis do escopo onde foram criadas. A relação entre this léxico das arrows e closures é mais próxima do que parece — as duas exploram o conceito de escopo léxico.

  • 05 - Funções — tudo sobre como funções funcionam por dentro: declaração, expressão, first-class functions e mais
  • 10 - Closures — o mecanismo de captura léxica que se relaciona diretamente com o this das arrow functions
  • Dicionário de JavaScript — verbetes de this, binding e contexto de execução

Referências