Herança Prototípica no JavaScript: prototype chain, Object.create e classes de uma vez por todas
← Voltar para Codeshort

Herança Prototípica no JavaScript: prototype chain, Object.create e classes de uma vez por todas

Prototype chain, __proto__, Object.create e class são a mesma coisa com rostos diferentes. Entenda o mecanismo real antes que um bug em produção te ensine.

DC
Dev Code Software
10 de junho de 2026·6 min de leitura

O bug que ninguém sabe explicar

Você já viu isso acontecer numa code review ou num bug report:

function FormConfig() {}
FormConfig.prototype.fields = [];

const formA = new FormConfig();
const formB = new FormConfig();

formA.fields.push("email");

console.log(formB.fields); // ["email"] — mas por quê?

formB nunca foi tocado. Mas o campo apareceu lá. Quem não entende prototype chain vai perder um tempo precioso antes de achar a causa.

Esse tipo de coisa não aparece em ambiente local. Aparece quando dois componentes instanciam a mesma classe base sem você perceber que estão compartilhando estado. Entender herança prototípica é a diferença entre debugar isso em dez minutos ou em uma hora.

O que é o prototype chain, de verdade

Todo objeto JavaScript tem uma referência interna chamada [[Prototype]]. Quando você acessa uma propriedade num objeto e ela não existe ali, o runtime sobe por essa cadeia de referências — objeto por objeto — até encontrar a propriedade ou chegar em null.

O processo é linear e previsível:

const veiculo = {
  mover() {
    return `${this.nome} em movimento`;
  },
};

const carro = Object.create(veiculo);
carro.nome = "Gol";
carro.buzinar = function () {
  return "bi bi";
};

console.log(carro.buzinar()); // "bi bi"        → próprio do objeto
console.log(carro.mover());   // "Gol em movimento" → veio do veiculo
console.log(carro.toString()); // "[object Object]" → veio do Object.prototype

Três níveis de busca numa chamada só. toString() não está em carro nem em veiculo — está no Object.prototype, o topo universal antes do null.

O custo de performance de subir essa cadeia é baixo para profundidades normais. O problema real é quando o código depende de herança profunda e o dev não sabe de onde uma propriedade está vindo.

💡 Dica: No DevTools do Chrome, expanda qualquer objeto e clique em [[Prototype]]. Você vê exatamente de onde cada método herdado está vindo — ferramenta subutilizada por 90% dos devs.

proto vs prototype: tabela e código

Essa é a confusão mais comum. São duas coisas com nomes parecidos e papéis opostos.

prototype__proto__ / Object.getPrototypeOf()
Existe emFunções construtorasTodos os objetos
O que éO objeto que instâncias vão herdarA referência ao prototype do objeto atual
Quando importaNa hora de usar newNa hora de acessar uma propriedade

Na prática:

function Produto(nome, preco) {
  this.nome = nome;
  this.preco = preco;
}

Produto.prototype.desconto = function (pct) {
  return this.preco * (1 - pct / 100);
};

const notebook = new Produto("Notebook", 3000);

console.log(notebook.hasOwnProperty("nome"));    // true  — é do objeto
console.log(notebook.hasOwnProperty("desconto")); // false — está no prototype

console.log(Object.getPrototypeOf(notebook) === Produto.prototype); // true

Quando você escreve new Produto(...), o JavaScript faz três coisas em sequência:

  1. Cria um objeto vazio
  2. Conecta [[Prototype]] desse objeto a Produto.prototype
  3. Executa Produto com this apontando pro novo objeto

Só isso. __proto__ ainda funciona na maioria dos browsers, mas é legado e está marcado como deprecated em alguns contextos. Em código novo, use sempre Object.getPrototypeOf().

⚠️ Atenção: Alterar __proto__ em runtime é uma das operações mais custosas do JavaScript. O V8 otimiza objetos baseados em sua "forma" (shape/hidden class). Mudar o prototype depois da criação invalida essas otimizações. Se você precisar de um objeto com prototype diferente, use Object.create() desde o início.

Object.create: quando usar e por quê

Object.create(proto) cria um objeto com o [[Prototype]] que você define explicitamente. Sem new, sem função construtora, sem cerimônia.

const repositorio = {
  salvar(entidade) {
    console.log(`Salvando ${entidade.id}`);
  },
  buscar(id) {
    console.log(`Buscando ${id}`);
  },
};

const usuarioRepo = Object.create(repositorio);
usuarioRepo.entidade = "usuario";

usuarioRepo.salvar({ id: 42 }); // "Salvando 42"

Mais útil ainda: criar um objeto sem herança nenhuma.

const config = {};
console.log("toString" in config);      // true — herdou do Object.prototype

const configLimpa = Object.create(null);
console.log("toString" in configLimpa); // false — objeto puro

Em dicionários e mapas de configuração onde você itera chaves com for...in ou verifica existência com in, herdar de Object.prototype pode causar falsos positivos. Object.create(null) elimina isso.

Redux usa esse padrão internamente. Node.js também. Não é exotismo — é precisão cirúrgica quando você precisa dela.

Classes são só syntax sugar — e prova disso

class em JavaScript não muda o modelo de herança. O que muda é a ergonomia.

class Animal {
  constructor(nome) {
    this.nome = nome;
  }
  falar() {
    return `${this.nome} faz um som`;
  }
}

class Gato extends Animal {
  falar() {
    return `${this.nome} mia`;
  }
}

const nami = new Gato("Nami");
console.log(nami.falar()); // "Nami mia"

console.log(Object.getPrototypeOf(Gato) === Animal);                   // true
console.log(Object.getPrototypeOf(Gato.prototype) === Animal.prototype); // true

extends cria dois links de prototype: um entre os construtores, outro entre os prototypes. É exatamente o que você faria manualmente com Object.create e Object.setPrototypeOf.

O que class acrescenta de real são os guardrails:

Animal("teste");
// TypeError: Class constructor Animal cannot be invoked without 'new'

class Cachorro extends Animal {
  constructor(nome, raca) {
    this.raca = raca;
    super(nome);
  }
}
const c = new Cachorro("Rex", "Lab");
// ReferenceError: Must call super constructor before accessing 'this'

Esses erros explícitos evitam bugs silenciosos. Em JS pré-ES6, chamar uma função construtora sem new criava propriedades no objeto global sem nenhum aviso.

Como debugar a cadeia de prototypes

Esse é o diferencial. Saber o conceito não ajuda quando você está na frente de um bug às 23h. O kit de ferramentas que funciona:

No código:

function inspecionarChain(obj) {
  let proto = obj;
  let nivel = 0;
  while (proto !== null) {
    console.log(`Nível ${nivel}:`, Object.keys(proto));
    proto = Object.getPrototypeOf(proto);
    nivel++;
  }
}

inspecionarChain(new Gato("teste"));
// Nível 0: ["nome"]          → propriedades da instância
// Nível 1: ["falar"]         → Gato.prototype
// Nível 2: ["falar"]         → Animal.prototype (versão base)
// Nível 3: [...métodos]      → Object.prototype
// Nível 4: null              → fim da cadeia

Para checar de onde uma propriedade vem:

function ondeEsta(obj, prop) {
  let alvo = obj;
  while (alvo !== null) {
    if (alvo.hasOwnProperty(prop)) {
      return alvo === obj ? "própria" : alvo.constructor?.name ?? "prototype anônimo";
    }
    alvo = Object.getPrototypeOf(alvo);
  }
  return "não encontrada";
}

No DevTools: selecione o objeto no Console, expanda [[Prototype]] e navegue pela árvore. Propriedades com fundo cinza são herdadas. As em negrito são próprias.

Erros que aparecem em produção

Estado mutável compartilhado via prototype

O exemplo do início deste post. A regra é simples: dados no construtor, métodos no prototype.

function FormBase() {
  this.fields = [];
}
FormBase.prototype.addField = function (f) {
  this.fields.push(f);
};

Agora cada instância tem seu próprio array fields.

instanceof quebrando entre contextos

const arr = new iframe.contentWindow.Array();
console.log(arr instanceof Array); // false — constructors diferentes
console.log(Array.isArray(arr));   // true — verifica estrutura, não referência

Iframes, Web Workers e módulos isolados têm seus próprios globals. instanceof compara referências de construtores — se os contextos forem diferentes, falha. Array.isArray e typeof são mais robustos nesses cenários.

Sobrescrever Object.prototype acidentalmente

Object.prototype.ativo = true;

const usuario = { nome: "Ana" };
for (const chave in usuario) {
  console.log(chave); // "nome", "ativo" — surprise
}

Adicionar qualquer coisa em Object.prototype contamina todos os objetos do runtime. Isso aparece em libs antigas (jQuery plugins mal escritos, por exemplo). Use Object.keys() ou verifique com hasOwnProperty dentro do for...in.

FAQ

Preciso entender prototype se só uso TypeScript e classes? Sim — talvez mais do que quem usa JavaScript puro. O TypeScript compila para JavaScript. Quando um bug de herança aparece em runtime, o stack trace expõe o prototype chain. Sem entender o mecanismo, você vai ficar olhando para o JavaScript gerado sem saber o que está acontecendo.

Qual a diferença prática entre Object.create() e new? new sempre passa por uma função construtora — a função roda, this é configurado, o prototype é conectado. Object.create() só faz a conexão de prototype, sem executar nenhuma função. É mais simples e mais explícito quando você não precisa de inicialização no construtor.

Por que class em JS não é igual a class em Java? Em Java, uma classe é um template — as instâncias são cópias independentes com sua própria memória. Em JavaScript, os objetos se ligam a outros objetos via referência. Métodos não são copiados, são compartilhados. Isso significa que mutar o prototype afeta todas as instâncias — algo impossível em Java.

hasOwnProperty ou Object.hasOwn()? Object.hasOwn(obj, prop) é a versão moderna (ES2022) e mais segura. hasOwnProperty pode ser sobrescrita se o objeto não herdar de Object.prototype (como no caso de Object.create(null)). Em código novo, prefira Object.hasOwn().

Como mixins funcionam com prototype? Mixins são cópias de propriedades, não links de prototype. Object.assign(Target.prototype, MixinA, MixinB) copia os métodos dos mixins para o prototype alvo. É herança múltipla via cópia, não via chain — o que significa que mudanças posteriores nos mixins não propagam automaticamente.

Próximos passos

Prototype é o fundamento. O próximo passo natural é entender como this se comporta dentro de métodos herdados quando passados como callback — é exatamente onde a maioria dos bugs reais acontece.

Depois disso: Proxies e Reflect, que são a camada sobre prototype que permite interceptar operações em objetos em runtime.

FerramentaCaso de uso ideal
Object.create(proto)Herança direta sem construtor
Object.create(null)Dicionários puros, sem colisão com Object.prototype
Função construtoraCompatibilidade com código legado
class / extendsHierarquias novas, mais de dois níveis
Object.assign()Mixins, composição sem herança

Leitura que vale o tempo: You Don't Know JS — this & Object Prototypes — gratuito, sem paywalls, e cobre exatamente o que a maioria dos cursos pula.