DEV Community

Lucas Pereira de Souza
Lucas Pereira de Souza

Posted on

Compreendendo 'this' no JavaScript

logotech

## Desvendando o this, bind, call, apply: Um Guia Definitivo para Especialistas Backend

No universo do JavaScript e, por extensão, do Node.js, o conceito de this pode ser um campo minado para desenvolvedores, especialmente quando se trata de gerenciar o contexto de execução em funções e métodos de classes. A forma como this se comporta pode variar drasticamente dependendo de como uma função é invocada, levando a erros sutis e difíceis de depurar. Felizmente, os métodos bind, call e apply nos oferecem um controle preciso sobre o this, permitindo-nos moldar o contexto de execução de acordo com nossas necessidades.

A Natureza Mutável do this

Em JavaScript, o valor de this é determinado dinamicamente no momento da invocação de uma função. Isso significa que this não se refere à função em si, nem ao local onde a função foi definida, mas sim ao objeto que invocou a função.

Considere o seguinte exemplo simples:

// Função simples
function greet() {
  console.log(`Olá, meu nome é ${this.name}`);
}

const person1 = { name: \"Alice\", sayHello: greet };
const person2 = { name: \"Bob\", sayHello: greet };

person1.sayHello(); // Saída: Olá, meu nome é Alice
person2.sayHello(); // Saída: Olá, meu nome é Bob
Enter fullscreen mode Exit fullscreen mode

Neste caso, quando person1.sayHello() é chamado, this dentro de greet se refere a person1. Da mesma forma, ao chamar person2.sayHello(), this se refere a person2.

No entanto, a situação se complica quando a função é passada como um callback ou quando não há um objeto explícito invocando-a:

function greet() {
  console.log(`Olá, meu nome é ${this.name}`);
}

const person = { name: \"Charlie\" };

// Invocação direta da função sem um objeto
greet(); // Saída: Olá, meu nome é undefined (em modo estrito, lançaria um erro)

// Passando a função como callback
setTimeout(person.sayHello, 100); // Saída: Olá, meu nome é undefined (ou erro)
Enter fullscreen mode Exit fullscreen mode

No cenário acima, this dentro de greet se refere ao objeto global (ou undefined em modo estrito) porque a função greet foi invocada sem um contexto específico.

Dominando o Contexto com call, apply e bind

Para resolver essa ambiguidade e garantir que this sempre aponte para o objeto desejado, JavaScript nos fornece três métodos poderosos: call, apply e bind.

1. call(): Invocação com Argumentos Individuais

O método call() invoca uma função com um valor this específico e argumentos fornecidos individualmente.

Sintaxe: funcao.call(thisArg, arg1, arg2, ...)

  • thisArg: O valor a ser passado como this para a função invocada.
  • arg1, arg2, ...: Argumentos individuais passados para a função.
function introduce(age: number, city: string): void {
  console.log(`Olá, sou ${this.name}, tenho ${age} anos e moro em ${city}.`);
}

const user = { name: \"Diana\" };

// Usando call para definir 'this' como 'user' e passar argumentos individualmente
introduce.call(user, 30, \"São Paulo\");
// Saída: Olá, sou Diana, tenho 30 anos e moro em São Paulo.
Enter fullscreen mode Exit fullscreen mode

2. apply(): Invocação com Array de Argumentos

O método apply() é semelhante ao call(), mas aceita os argumentos da função como um array.

Sintaxe: funcao.apply(thisArg, [argsArray])

  • thisArg: O valor a ser passado como this para a função invocada.
  • argsArray: Um array cujos elementos serão passados como argumentos para a função.
function describe(hobbies: string[], occupation: string): void {
  console.log(`Meus hobbies são: ${hobbies.join(', ')}. Minha ocupação é ${occupation}.`);
}

const developer = { name: \"Ethan\" };

const devHobbies = [\"programar\", \"ler\", \"viajar\"];
const devOccupation = \"Engenheiro de Software\";

// Usando apply para definir 'this' como 'developer' e passar argumentos como um array
describe.apply(developer, [devHobbies, devOccupation]);
// Saída: Meus hobbies são: programar, ler, viajar. Minha ocupação é Engenheiro de Software.
Enter fullscreen mode Exit fullscreen mode

3. bind(): Criação de uma Nova Função com Contexto Fixo

Ao contrário de call e apply, que invocam a função imediatamente, bind() cria uma nova função com o this permanentemente vinculado a um valor específico. Os argumentos também podem ser pré-definidos.

Sintaxe: funcao.bind(thisArg, arg1, arg2, ...)

  • thisArg: O valor a ser vinculado como this na nova função.
  • arg1, arg2, ... (opcional): Argumentos pré-definidos que serão passados para a função quando ela for invocada.
function displayProduct(price: number): void {
  console.log(`Produto: ${this.productName}, Preço: R$${price.toFixed(2)}`);
}

const gadget = { productName: \"Smartphone XYZ" };

// Criando uma nova função 'showGadgetPrice' com 'this' vinculado a 'gadget'
const showGadgetAndPrice = displayProduct.bind(gadget);

// Invocando a nova função com o argumento 'price'
showGadgetAndPrice(1299.99);
// Saída: Produto: Smartphone XYZ, Preço: R$1299.99

// Bind com argumentos pré-definidos
function multiply(factor: number): number {
    return this.value * factor;
}

const numberContext = { value: 10 };

const multiplyByFive = multiply.bind(numberContext, 5); // 'factor' já é 5

console.log(multiplyByFive()); // Saída: 50
Enter fullscreen mode Exit fullscreen mode

this em Classes e Métodos

Em classes, o this geralmente se refere à instância da classe. No entanto, ao passar métodos de classe como callbacks, o contexto original pode ser perdido, exigindo o uso de bind.

class Counter {
  count: number = 0;

  increment(): void {
    this.count++;
    console.log(`Contagem: ${this.count}`);
  }
}

const myCounter = new Counter();

// Problema: 'increment' perde o contexto 'this' quando passado como callback
// setTimeout(myCounter.increment, 1000); // Chamaria increment sem o contexto correto

// Solução 1: Usar bind no construtor
class CounterWithBind {
  count: number = 0;

  constructor() {
    // Vincula permanentemente o método 'increment' à instância 'this'
    this.increment = this.increment.bind(this);
  }

  increment(): void {
    this.count++;
    console.log(`Contagem (Bind no Construtor): ${this.count}`);
  }
}

const myCounterWithBind = new CounterWithBind();
setTimeout(myCounterWithBind.increment, 1500); // Agora funciona corretamente

// Solução 2: Usar bind na hora da passagem (menos comum em construtores)
const myCounter2 = new Counter();
setTimeout(myCounter2.increment.bind(myCounter2), 2000); // Vincula no momento da chamada
Enter fullscreen mode Exit fullscreen mode

Conclusão

Compreender e controlar o this é fundamental para escrever código JavaScript robusto e previsível, especialmente em arquiteturas backend complexas. call, apply e bind são ferramentas indispensáveis em nosso arsenal, permitindo-nos gerenciar o contexto de execução com precisão. Enquanto call e apply são úteis para invocações imediatas com diferentes formas de passar argumentos, bind se destaca na criação de funções com contexto fixo, ideal para callbacks e padrões de design onde o contexto deve ser imutável. Dominar essas técnicas não só previne erros comuns, mas também eleva a qualidade e a manutenibilidade do seu código backend.

Top comments (0)