Desafio JavaScript

Contador com contexto preservado

Enunciado

Crie uma classe Contador que possui um valor interno e um método incrementar que aumenta o valor em 1 e imprime o novo valor. O método incrementar deve funcionar mesmo quando extraído da instância (por exemplo, const inc = contador.incrementar; inc();). Implemente duas versões: uma usando bind no construtor e outra usando arrow function como método de classe. Teste ambas.

Requisitos

  • A classe Contador deve ter um valor inicial 0.
  • O método incrementar deve incrementar e imprimir o valor.
  • A versão com bind deve fixar o this no construtor.
  • A versão com arrow function deve ser um método de classe definido como arrow function.
  • Ambas devem funcionar quando o método é extraído.

Código inicial

// Versão 1: bind no construtor
class ContadorBind {
  constructor() {
    this.valor = 0;
    // complete
  }
  incrementar() {
    // complete
  }
}

// Versão 2: arrow function
class ContadorArrow {
  constructor() {
    this.valor = 0;
  }
  // complete com arrow function
}

// Teste
const c1 = new ContadorBind();
const inc1 = c1.incrementar;
inc1();
inc1();

const c2 = new ContadorArrow();
const inc2 = c2.incrementar;
inc2();
inc2();

Saída esperada

1
2
1
2
Ver dica

Na versão com bind, use this.incrementar = this.incrementar.bind(this) no construtor. Na versão com arrow, defina incrementar = () => { ... } como propriedade de instância.

Mostrar solução
// Versão 1: bind no construtor
class ContadorBind {
  constructor() {
    this.valor = 0;
    // Fixa o this do método incrementar à instância
    this.incrementar = this.incrementar.bind(this);
  }
  incrementar() {
    this.valor++;
    console.log(this.valor);
  }
}

// Versão 2: arrow function como método
class ContadorArrow {
  constructor() {
    this.valor = 0;
  }
  // Arrow function captura o this da instância
  incrementar = () => {
    this.valor++;
    console.log(this.valor);
  };
}

// Teste da versão com bind
const c1 = new ContadorBind();
const inc1 = c1.incrementar; // extrai o método
inc1(); // 1
inc1(); // 2

// Teste da versão com arrow
const c2 = new ContadorArrow();
const inc2 = c2.incrementar; // extrai o método
inc2(); // 1
inc2(); // 2

Passo a passo

  1. Na classe ContadorBind, o construtor inicializa this.valor = 0.
  2. Ainda no construtor, this.incrementar = this.incrementar.bind(this) cria uma nova função ligada à instância e a atribui como propriedade própria, sobrescrevendo o método do protótipo.
  3. O método incrementar incrementa this.valor e imprime.
  4. Na classe ContadorArrow, o método incrementar é definido como uma arrow function no corpo da classe, o que o torna uma propriedade de instância (não do protótipo) e captura o this léxico da instância.
  5. Criamos instâncias e extraímos seus métodos para variáveis (inc1, inc2).
  6. Chamamos as funções extraídas; como o this está fixado, ambas incrementam o valor correto e imprimem 1 e 2.

Por que funciona

Na versão com bind, o método original é substituído por uma versão ligada à instância, então mesmo quando chamado sem contexto, this é a instância. Na versão com arrow function, o this é capturado do escopo onde a arrow foi definida, que é o construtor da classe (a instância). Assim, ambos resolvem a perda de contexto que ocorreria com um método normal extraído.

Erros comuns

  • Esquecer de reatribuir no construtor: this.incrementar.bind(this); sem atribuir não faz efeito. Use this.incrementar = this.incrementar.bind(this);.
  • Usar arrow function no protótipo: ContadorArrow.prototype.incrementar = () => { ... } não captura o this da instância, pois o escopo léxico é o módulo. Defina como propriedade de instância no corpo da classe.
  • Chamar bind após extrair: const inc = c.incrementar; inc.bind(c)(); funciona, mas não é a solução pedida (bind no construtor).
  • Confundir this em arrow function de classe: Arrow functions em classes não têm this próprio; se usadas incorretamente, podem referenciar o escopo global.

Outra forma de resolver

Podemos usar bind diretamente ao extrair: const inc = c1.incrementar.bind(c1);. Isso é útil quando não queremos modificar a classe, mas é menos eficiente se o método for extraído várias vezes. A solução com bind no construtor ou arrow function é preferível para garantir que o método sempre funcione.

Saída esperada

1
2
1
2