voltar aos artigosobject-oriented js
$cd~/artigos/inheritance-polymorphism-heranca-javascript
Object-Oriented JS
série 33 Conceitos JS·parte 20 de 38

Inheritance & Polymorphism: Herança e Polimorfismo em JavaScript

18 minVanderlei José Alamino

Como jogos criam centenas de tipos de personagens sem copiar código? Como Guerreiro, Mago e Arqueiro podem todos atacar de formas diferentes, mas ser tratados igualmente em batalha?

A resposta está em herança (classes filhas reutilizam código do pai) e polimorfismo (objetos diferentes respondem à mesma chamada de método de formas únicas).

snippetJavaScript
class Personagem {  constructor(nome) { this.nome = nome; }  atacar() { return `${this.nome} ataca!`; }} class Guerreiro extends Personagem {  atacar() { return `${this.nome} brande uma espada!`; }} function executarBatalha(personagens) {  personagens.forEach(p => console.log(p.atacar()));}

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você vai aprender:

  • Herança com extends e super
  • Sobrescrita de métodos e polimorfismo na prática
  • Composição vs herança (problema do Gorila-Banana)
  • Mixins para compartilhar comportamento

Pré-requisitos: Factories & Classes, Prototypes & Object Creation e this, call, apply & bind.

inheritance-polymorphism-heranca-javascript.meta.jsonjson

// metadados do artigo

{
  "tag": "object-oriented js",
  "readTime": "18 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 20,
    "total": 38
  }
}
abrir fonte original
inheritance-polymorphism-heranca-javascript.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

O que é Herança?

Herança é quando uma classe filha (subclasse) recebe propriedades e métodos de uma classe pai (superclasse). A relação é "É-UM": um Guerreiro é um Personagem.

snippetJavaScript
class Personagem {  constructor(nome, vida = 100) {    this.nome = nome;    this.vida = vida;  }  apresentar() { return `Eu sou ${this.nome} com ${this.vida} HP`; }  atacar() { return `${this.nome} ataca!`; }  receberDano(qtd) {    this.vida -= qtd;    return `${this.nome} recebe ${qtd} de dano! (${this.vida} HP)`;  }} class Guerreiro extends Personagem {  constructor(nome) {    super(nome, 150);    this.furia = 0;  }  gritoDeGuerra() {    this.furia += 10;    return `${this.nome} grita com fúria! Fúria: ${this.furia}`;  }} const conan = new Guerreiro("Conan");console.log(conan.apresentar());    // herdadoconsole.log(conan.gritoDeGuerra());   // específicoconsole.log(conan.atacar());          // herdado

Princípio DRY: escreva código comum uma vez no pai — filhas herdam correções e melhorias automaticamente.

02

seção

Analogia: Personagens de Jogo

snippettext
                    Personagem (pai)                   nome, vida, atacar()                          │         ┌────────────────┼────────────────┐         ▼                ▼                ▼    Guerreiro          Mago            Arqueiro    furia, ⚔           mana, ✨         flechas, 🏹    atacar()           atacar()         atacar()    (sobrescreve)      (sobrescreve)    (sobrescreve)

Cada filho herda de Personagem mas sobrescreve atacar() — comportamento especializado com interface comum. Isso é polimorfismo.

03

seção

Herança com extends

snippetJavaScript
class Personagem {  constructor(nome) { this.nome = nome; this.vida = 100; }  atacar() { return `${this.nome} ataca!`; }} class Mago extends Personagem {  constructor(nome) {    super(nome);    this.mana = 100;  }  lançarMagia(magia) {    this.mana -= 10;    return `${this.nome} lança ${magia}!`;  }} const gandalf = new Mago("Gandalf");console.log(gandalf.atacar());                    // herdadoconsole.log(gandalf.lançarMagia("Bola de Fogo")); // específico

A filha herda: propriedades de instância, métodos, métodos estáticos, getters/setters.

snippetJavaScript
class Personagem {  get estaVivo() { return this.vida > 0; }  static criarAleatorio() { return new this("Herói"); }}class Guerreiro extends Personagem { /* ... */ } Guerreiro.criarAleatorio();  // estático herdado
04

seção

A Palavra-Chave super

super() — construtor do pai

Obrigatório antes de usar this em construtores filhos:

snippetJavaScript
class Guerreiro extends Personagem {  constructor(nome) {    super(nome, 150);  // PRIMEIRO    this.furia = 0;    // depois  }} // ❌ this.furia = 0 antes de super() → ReferenceError

super.metodo() — estender o pai

snippetJavaScript
class Guerreiro extends Personagem {  atacar() {    const base = super.atacar();    return `${base} com uma espada!`;  }  descrever() {    return `${super.descrever()} - Classe Guerreiro`;  }}

Padrão: estender com super.metodo(), não substituir cegamente — preserva comportamento do pai.

05

seção

Sobrescrita de Métodos

Mesmo nome de método, implementação diferente na filha — sombreia a do pai:

snippetJavaScript
class Guerreiro extends Personagem {  atacar() { return `${this.nome} brande espada — 25 de dano!`; }}class Mago extends Personagem {  atacar() { return `${this.nome} lança bola de fogo — 30 de dano!`; }}class Arqueiro extends Personagem {  atacar() { return `${this.nome} dispara flecha — 20 de dano!`; }}
MotivoExemplo
EspecializaçãoCada classe ataca diferente
Extensãosuper.atacar() + logging
CustomizaçãoValores padrão distintos
06

seção

Polimorfismo

Polimorfismo ("muitas formas"): objetos diferentes respondem à mesma chamada de formas únicas — trate tipos distintos por uma interface comum.

snippetJavaScript
function executarBatalha(personagens) {  console.log("⚔️ Batalha começa!");  personagens.forEach(p => console.log(p.atacar()));} const grupo = [  new Guerreiro("Conan"),  new Mago("Gandalf"),  new Arqueiro("Legolas"),  new Personagem("Aldeão"),]; executarBatalha(grupo);// Cada um ataca do seu jeito — sem if/else por tipo!
snippettext
SEM polimorfismo          COM polimorfismo─────────────────         ─────────────────if (p instanceof Guerreiro)  p.atacar()  p.brandirEspada()         // funciona com qualquer Personagemelse if (p instanceof Mago)  p.conjurarMagia()// + código a cada novo tipo
BenefícioExplicação
Aberto para extensãoNovos tipos sem mudar executarBatalha
Acoplamento fracoFunção não conhece tipos concretos
Código limpoSem switch/if infinitos

instanceof verifica a cadeia de protótipos:

snippetJavaScript
console.log(guerreiro instanceof Guerreiro);   // trueconsole.log(guerreiro instanceof Personagem);  // trueconsole.log(guerreiro instanceof Mago);        // false
07

seção

Por Baixo dos Panos: Protótipos

class e extends são açúcar sintático sobre protótipos:

snippetJavaScript
class Guerreiro extends Personagem {  gritoDeGuerra() { return `${this.nome} grita!`; }} // guerreiro.atacar() → busca em Guerreiro.prototype → Personagem.prototype
snippettext
guerreiro (instância)    │ [[Prototype]]    ▼Guerreiro.prototype (gritoDeGuerra)    │ [[Prototype]]    ▼Personagem.prototype (atacar) ← encontrado aqui    │ [[Prototype]]    ▼Object.prototype → null

Para mergulho profundo, veja Prototypes & Object Creation.

08

seção

Composição vs Herança

Problema do Gorila-Banana: você queria uma banana, mas herdou o gorila e a selva inteira.

snippettext
Entidade → EntidadeViva → Animal → Mamífero → Morcego// E o PeixeVoador? Pinguim que não voa? Hierarquia frágil.
TesteSe sim…Exemplo
É-UMHerançaGuerreiro é um Personagem
TEM-UMComposiçãoPersonagem tem inventário

Composição — misture comportamentos:

snippetJavaScript
const podeVoar = (estado) => ({ voar() { return `${estado.nome} voa!`; } });const podeConjurar = (estado) => ({ conjurar(m) { return `${estado.nome} conjura ${m}!`; } }); function criarMagoVoador(nome) {  const estado = { nome, vida: 100, mana: 50 };  return { ...estado, ...podeVoar(estado), ...podeConjurar(estado) };}
Use herança quandoUse composição quando
Relação clara É-UMRelação TEM-UM
Filha usa maioria do paiSó alguns comportamentos
Hierarquia rasa (≤3 níveis)Misturar comportamentos livremente
Relações estáveisRequisitos mudam muito

Regra de ouro: Prefira composição sobre herança. Use herança só com É-UM claro e estável.

09

seção

Mixins

Mixins adicionam funcionalidade sem herança múltipla:

snippetJavaScript
const Nadador = { nadar() { return `${this.nome} nada!`; } };const Voador = { voar() { return `${this.nome} voa!`; } }; class Animal { constructor(nome) { this.nome = nome; } } class Pato extends Animal {}Object.assign(Pato.prototype, Nadador, Voador); const donald = new Pato("Donald");donald.nadar(); donald.voar();

Mixin funcional (empilhamento de classes):

snippetJavaScript
const comLogging = (Base) => class extends Base {  log(msg) { console.log(`[${this.nome}]: ${msg}`); }}; class PersonagemLogado extends comLogging(Personagem) {  realizarAcao() { this.log("Ação!"); }}

Útil para: logging, serialização, eventos, múltiplos comportamentos. Cuidado com colisões de nomes e dependências ocultas.

10

seção

Erros Comuns

Esquecer super() ou usar this antes

snippetJavaScript
// ❌ ReferenceErrorclass Filha extends Pai {  constructor() { this.x = 1; super(); }}// ✅class Filha extends Pai {  constructor() { super(); this.x = 1; }}

Hierarquias profundas demais

Mantenha ≤3 níveis. Prefira composição para ramificações complexas.

Herdar só para reutilizar código

snippetJavaScript
// ❌ Pilha NÃO é um Arrayclass Pilha extends Array { peek() { return this.atEnd; } } // ✅ Pilha TEM um arrayclass Pilha {  #itens = [];  push(i) { this.#itens.push(i); }  pop() { return this.#itens.pop(); }}
snippettext
É relação É-UM? → SIM → Filha usa maioria do pai? → SIM → ≤3 níveis? → SIM → Herança ✓Caso contrário → Composição ou Mixins
11

seção

Perguntas Clássicas de Entrevista

12

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • Herança com extends — filhas reutilizam código do pai (relação É-UM)
  • super() primeiro no construtor filho — antes de qualquer this
  • super.metodo() estende comportamento do pai em overrides
  • Polimorfismo — mesma interface, respostas diferentes; evita if/else por tipo
  • Classes ES6 são açúcar sobre a cadeia de protótipos
  • É-UM → herança; TEM-UM → composição — prefira composição na dúvida
  • Gorila-Banana — herança profunda traz bagagem desnecessária
  • Mixins compartilham comportamento sem herança múltipla
  • Hierarquias rasas (≤3 níveis) e instanceof para checagem de tipo

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.