- O bug que ninguém sabe explicar
- O que é o prototype chain, de verdade
- proto vs prototype: tabela e código
- Object.create: quando usar e por quê
- Classes são só syntax sugar — e prova disso
- Como debugar a cadeia de prototypes
- Erros que aparecem em produção
- FAQ
- Próximos passos
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 em | Funções construtoras | Todos os objetos |
| O que é | O objeto que instâncias vão herdar | A referência ao prototype do objeto atual |
| Quando importa | Na hora de usar new | Na 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:
- Cria um objeto vazio
- Conecta
[[Prototype]]desse objeto aProduto.prototype - Executa
Produtocomthisapontando 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, useObject.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.
| Ferramenta | Caso 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 construtora | Compatibilidade com código legado |
class / extends | Hierarquias 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.