As classes em JavaScript foram introduzidas no ECMAScript 2015 (ES6) como uma forma mais clara e orientada a objetos de trabalhar com herança e protótipos. Elas não introduzem um novo modelo de herança, mas fornecem uma sintaxe mais familiar para programadores vindos de outras linguagens como Java ou C++. Nesta aula, vamos explorar os componentes essenciais de uma classe: sintaxe, construtor, métodos, campos e a palavra-chave this.

Com as classes, você pode organizar seu código de maneira mais modular, reutilizável e legível, especialmente em aplicações de médio e grande porte. Vamos ver cada parte em detalhes, com exemplos práticos que você pode executar em seu ambiente de desenvolvimento.

Sintaxe

A sintaxe de uma classe em JavaScript é declarada com a palavra-chave class, seguida do nome da classe (geralmente iniciando com letra maiúscula, por convenção) e um bloco de chaves {}. Dentro desse bloco, definimos os membros da classe: construtor, métodos e campos.

Exemplo básico de declaração de classe:

class Pessoa {
  // corpo da classe
}

Diferente de funções construtoras, as classes não são hoisted (elevadas). Ou seja, você não pode usar uma classe antes de declará-la no código. Isso evita erros de referência e torna o código mais previsível.

Podemos também utilizar expressões de classe, atribuindo a classe a uma variável:

const Animal = class {
  // corpo da classe
};

Essa forma é útil quando você precisa criar classes dinamicamente ou passá-las como argumento para funções.

Construtor

O construtor é um método especial chamado automaticamente quando uma nova instância da classe é criada com a palavra-chave new. Ele é usado para inicializar as propriedades do objeto. O método construtor é definido com o nome constructor.

Exemplo:

class Pessoa {
  constructor(nome, idade) {
    this.nome = nome;
    this.idade = idade;
  }
}

const pessoa1 = new Pessoa('Ana', 30);
console.log(pessoa1.nome); // Ana
console.log(pessoa1.idade); // 30

Se você não definir um construtor, a classe terá um construtor padrão que simplesmente cria o objeto sem inicializar propriedades. Você pode ter apenas um construtor por classe, mas pode definir valores padrão para os parâmetros, como em qualquer função.

class Produto {
  constructor(nome = 'Genérico', preco = 0) {
    this.nome = nome;
    this.preco = preco;
  }
}

const p1 = new Produto();
console.log(p1.nome); // Genérico
console.log(p1.preco); // 0

Métodos

Métodos são funções definidas dentro da classe que descrevem o comportamento das instâncias. Eles são definidos sem a palavra-chave function e são adicionados ao protótipo da classe, o que significa que todas as instâncias compartilham o mesmo método, economizando memória.

Exemplo de método:

class Calculadora {
  somar(a, b) {
    return a + b;
  }
  subtrair(a, b) {
    return a - b;
  }
}

const calc = new Calculadora();
console.log(calc.somar(5, 3)); // 8
console.log(calc.subtrair(5, 3)); // 2

Além dos métodos de instância, você pode definir métodos estáticos com a palavra-chave static. Esses métodos são chamados diretamente na classe, não em instâncias. Eles são úteis para funções utilitárias relacionadas à classe.

class Matematica {
  static dobrar(numero) {
    return numero * 2;
  }
}

console.log(Matematica.dobrar(4)); // 8

Também é possível definir getters e setters usando get e set para controlar o acesso a propriedades.

class Temperatura {
  constructor(celsius) {
    this._celsius = celsius;
  }
  get celsius() {
    return this._celsius;
  }
  set celsius(valor) {
    if (valor < -273.15) {
      throw new Error('Temperatura abaixo do zero absoluto');
    }
    this._celsius = valor;
  }
}

const temp = new Temperatura(25);
console.log(temp.celsius); // 25
temp.celsius = 30;
console.log(temp.celsius); // 30

Campos

Campos (ou propriedades de classe) são variáveis declaradas diretamente na classe, que podem ter valores iniciais. Antes do ES2022, era comum declarar campos apenas no construtor. Agora, podemos declará-los no corpo da classe, tornando o código mais explícito.

Exemplo:

class Contador {
  valor = 0; // campo público
  static total = 0; // campo estático

  incrementar() {
    this.valor++;
    Contador.total++;
  }
}

const c1 = new Contador();
c1.incrementar();
c1.incrementar();
console.log(c1.valor); // 2
console.log(Contador.total); // 2

Além dos campos públicos, temos campos privados, que são precedidos por #. Eles são acessíveis apenas dentro da classe, garantindo encapsulamento real.

class Pessoa {
  #nome; // campo privado

  constructor(nome) {
    this.#nome = nome;
  }

  getNome() {
    return this.#nome;
  }
}

const pessoa = new Pessoa('João');
console.log(pessoa.getNome()); // João
// console.log(pessoa.#nome); // Erro: SyntaxError

Os campos privados são uma adição recente (ES2022) e são suportados na maioria dos navegadores modernos e no Node.js.

this

A palavra-chave this é um dos conceitos mais confusos em JavaScript. Dentro de uma classe, this refere-se à instância atual do objeto. Ele é usado para acessar propriedades e métodos da própria instância.

Exemplo:

class Cachorro {
  constructor(nome) {
    this.nome = nome;
  }
  latir() {
    console.log(`${this.nome} está latindo!`);
  }
}

const rex = new Cachorro('Rex');
rex.latir(); // Rex está latindo!

No entanto, this pode mudar de contexto dependendo de como a função é chamada. Por exemplo, se você passar um método como callback, o this pode ser perdido. Para contornar isso, você pode usar funções de seta (arrow functions) ou o método bind.

class Botao {
  constructor() {
    this.clicado = 0;
  }
  clicar() {
    this.clicado++;
    console.log(`Clicado ${this.clicado} vezes`);
  }
}

const botao = new Botao();
// Perda de contexto se passarmos como callback
setTimeout(botao.clicar, 1000); // erro: this é undefined
// Solução: usar arrow function no construtor ou bind
class Botao2 {
  constructor() {
    this.clicado = 0;
    this.clicar = this.clicar.bind(this);
  }
  clicar() {
    this.clicado++;
    console.log(`Clicado ${this.clicado} vezes`);
  }
}

const botao2 = new Botao2();
setTimeout(botao2.clicar, 1000); // funciona

Uma alternativa moderna é usar campos de classe com arrow functions, que capturam o this do escopo onde foram definidos.

class Botao3 {
  clicado = 0;
  clicar = () => {
    this.clicado++;
    console.log(`Clicado ${this.clicado} vezes`);
  };
}

const botao3 = new Botao3();
setTimeout(botao3.clicar, 1000); // funciona

Entender this é crucial para evitar bugs sutis em seu código.

Boas Práticas

  • Use nomes de classes em PascalCase.
  • Declare campos no topo da classe para facilitar a leitura.
  • Prefira campos privados para dados sensíveis.
  • Evite métodos grandes; divida em métodos menores.
  • Use métodos estáticos para funções que não dependem de instância.
  • Use getters/setters para validar dados.

Referências

Exercícios

  1. Crie uma classe chamada Livro com construtor que receba titulo e autor. Adicione um método exibirInfo() que retorna uma string com o título e autor.

    ✓ Resposta:
    class Livro {
      constructor(titulo, autor) {
        this.titulo = titulo;
        this.autor = autor;
      }
      exibirInfo() {
        return `${this.titulo} - ${this.autor}`;
      }
    }
    
    const livro = new Livro('O Senhor dos Anéis', 'J.R.R. Tolkien');
    console.log(livro.exibirInfo()); // O Senhor dos Anéis - J.R.R. Tolkien
  2. Crie uma classe ContaBancaria com campos privados #saldo e métodos depositar(valor) e sacar(valor), que atualizam o saldo. Adicione um getter para saldo.

    ✓ Resposta:
    class ContaBancaria {
      #saldo = 0;
    
      depositar(valor) {
        if (valor > 0) {
          this.#saldo += valor;
        }
      }
    
      sacar(valor) {
        if (valor > 0 && valor <= this.#saldo) {
          this.#saldo -= valor;
        }
      }
    
      get saldo() {
        return this.#saldo;
      }
    }
    
    const conta = new ContaBancaria();
    conta.depositar(100);
    conta.sacar(30);
    console.log(conta.saldo); // 70
  3. Escreva uma classe Pessoa com um método estático criar que recebe nome e idade e retorna uma nova instância. Além disso, adicione um método de instância apresentar() que exibe uma mensagem.

    ✓ Resposta:
    class Pessoa {
      constructor(nome, idade) {
        this.nome = nome;
        this.idade = idade;
      }
    
      static criar(nome, idade) {
        return new Pessoa(nome, idade);
      }
    
      apresentar() {
        console.log(`Olá, meu nome é ${this.nome} e tenho ${this.idade} anos.`);
      }
    }
    
    const pessoa = Pessoa.criar('Maria', 25);
    pessoa.apresentar(); // Olá, meu nome é Maria e tenho 25 anos.
  4. Explique o que acontece com o this quando você passa um método de uma classe como callback e como resolver esse problema usando arrow function. Dê um exemplo.

    ✓ Resposta:

    Quando passamos um método como callback, ele perde o contexto do objeto, então this se torna undefined (em modo estrito) ou o objeto global. Para resolver, podemos usar uma arrow function que captura o this do escopo onde foi definida, ou usar .bind().

    class Relogio {
      constructor() {
        this.hora = 0;
        // arrow function como método de classe
        this.tick = () => {
          this.hora++;
          console.log(this.hora);
        };
      }
    }
    
    const relogio = new Relogio();
    setInterval(relogio.tick, 1000); // funciona, this não é perdido
  5. Crie uma classe Carro com um campo público marca e um campo privado #velocidade inicializado em 0. Adicione métodos acelerar() e frear() que alteram a velocidade, e um getter para velocidade.

    ✓ Resposta:
    class Carro {
      marca;
      #velocidade = 0;
    
      constructor(marca) {
        this.marca = marca;
      }
    
      acelerar() {
        this.#velocidade += 10;
      }
    
      frear() {
        if (this.#velocidade >= 10) {
          this.#velocidade -= 10;
        }
      }
    
      get velocidade() {
        return this.#velocidade;
      }
    }
    
    const carro = new Carro('Toyota');
    carro.acelerar();
    carro.acelerar();
    carro.frear();
    console.log(carro.velocidade); // 10