JavaScript nasceu com um modelo de objetos baseado em protótipos. Mesmo após a chegada da sintaxe de class (ES2015), o mecanismo fundamental continua sendo o mesmo: delegação via prototype chain. Por isso, entender a herança “antes das classes” segue relevante — tanto para manter sistemas legados quanto para escrever código mais previsível e seguro.
Neste artigo, o foco é explicar JavaScript Herança Classes a partir do que acontece por baixo do capô: como a cadeia de protótipos resolve propriedades, como Object.create() cria objetos com protótipo explícito e quais armadilhas de segurança e manutenção aparecem nesse caminho.
O que é “herança” em JavaScript (sem classes)
Em linguagens clássicas orientadas a objetos, herança geralmente significa “uma classe filha herda da classe pai”. Em JavaScript, o modelo é diferente:
- Objetos podem delegar a busca de propriedades para outro objeto (o protótipo).
- Essa delegação é encadeada: se não achar em um objeto, procura no protótipo; se não achar, procura no protótipo do protótipo, e assim por diante.
Esse encadeamento é chamado de prototype chain (cadeia de protótipos). Portanto, a “herança” em JavaScript é, na prática, compartilhamento e reutilização por delegação, não uma cópia de membros.
Prototype chain: como a resolução de propriedades funciona
Quando você acessa obj.algo, o motor do JavaScript segue, em termos práticos, esta ordem:
- Procura a propriedade
algono próprioobj(propriedade “own”). - Se não existir, procura no protótipo de
obj. - Se ainda não existir, sobe a cadeia até chegar em
null.
Um exemplo simples:
const base = {
tipo: "base",
describe() {
return `Tipo: ${this.tipo}`;
},
};
const obj = Object.create(base);
obj.tipo = "filho";
console.log(obj.describe()); // "Tipo: filho"
O método describe() não está em obj — está em base. Ao chamar obj.describe(), o JavaScript encontra describe no protótipo, mas executa com this apontando para obj. Esse detalhe é central: o método é compartilhado, mas o estado pode estar no objeto “filho”.
Verificando a cadeia na prática
Algumas formas de inspecionar isso:
console.log(Object.getPrototypeOf(obj) === base); // true
console.log(obj.hasOwnProperty("describe")); // false
console.log("describe" in obj); // true
hasOwnPropertyverifica se a propriedade é do próprio objeto.- O operador
inverifica se a propriedade existe em algum ponto da cadeia.
Object.create(): herança explícita por delegação
Object.create(proto) cria um novo objeto cujo protótipo é proto. Isso permite compor objetos de forma direta, sem construtores e sem new.
Padrão básico com Object.create()
const usuarioBase = {
init(nome) {
this.nome = nome;
return this;
},
saudacao() {
return `Olá, ${this.nome}`;
},
};
const usuario = Object.create(usuarioBase).init("Ana");
console.log(usuario.saudacao()); // "Olá, Ana"
Esse padrão (às vezes chamado de “prototypal inheritance” ou “OLOO” — Objects Linked to Other Objects) tem algumas características:
usuarioBasefunciona como “modelo” de comportamento.- Objetos concretos são criados e ligados a esse modelo.
- Não há “classe”; há um objeto delegando para outro.
Criando com propriedades já definidas (descriptors)
Object.create também aceita um segundo argumento com property descriptors, útil para controlar writable, enumerable, configurable:
const contaBase = {
saldo() {
return this._saldo;
},
};
const conta = Object.create(contaBase, {
_saldo: { value: 100, writable: true, enumerable: false },
});
console.log(conta.saldo()); // 100
console.log(Object.keys(conta)); // []
Isso é relevante para reduzir exposição acidental de dados em loops e serializações, embora não seja um mecanismo de segurança por si só.
Antes das classes: função construtora e prototype
Historicamente, o padrão mais comum para “JavaScript Herança Classes” antes do class era a função construtora:
function Pessoa(nome) {
this.nome = nome;
}
Pessoa.prototype.saudacao = function () {
return `Olá, ${this.nome}`;
};
const p = new Pessoa("Bruno");
console.log(p.saudacao());
newcria um objeto novo.- Define o protótipo do novo objeto como
Pessoa.prototype. - Executa
Pessoacomthisapontando para o novo objeto.
Esse modelo é prototípico, mas tem semântica mais “parecida com classe”, e por isso foi amplamente adotado.
Herança entre “tipos” com construtores (padrão clássico)
Um padrão tradicional:
function Animal(nome) {
this.nome = nome;
}
Animal.prototype.falar = function () {
return `${this.nome} faz um som.`;
};
function Cachorro(nome) {
Animal.call(this, nome);
}
Cachorro.prototype = Object.create(Animal.prototype);
Cachorro.prototype.constructor = Cachorro;
Cachorro.prototype.falar = function () {
return `${this.nome} late.`;
};
const dog = new Cachorro("Rex");
console.log(dog.falar());
O que acontece aqui, passo a passo:
Animal.call(this, nome)copia a inicialização deAnimalpara o objetoCachorro.Cachorro.prototype = Object.create(Animal.prototype)conecta os protótipos.- Ajusta-se
constructorpor convenção (útil para inspeção e algumas ferramentas). - Métodos específicos são adicionados em
Cachorro.prototype.
Esse padrão antecede class extends, mas o mecanismo subjacente é o mesmo.
O que muda com class (e o que não muda)
A sintaxe class organiza o código e reduz armadilhas, mas:
classnão substitui protótipos; ela apenas oferece uma camada sintática.- Métodos declarados em
classvão paraprototype. extendsconfigura a cadeia de protótipos de forma automática.
Saber isso é útil ao depurar problemas de herança, sombras de propriedades (shadowing), this e comportamento inesperado em objetos.
Armadilhas comuns (e como evitá-las)
1) Compartilhar estado mutável no protótipo
Colocar arrays/objetos no protótipo pode fazer diferentes instâncias compartilharem o mesmo estado:
const base = {
itens: [],
};
const a = Object.create(base);
const b = Object.create(base);
a.itens.push("x");
console.log(b.itens); // ["x"] (surpresa)
Como evitar: inicialize estado mutável em cada objeto, não no protótipo:
const baseSeguro = {
init() {
this.itens = [];
return this;
},
};
const a2 = Object.create(baseSeguro).init();
const b2 = Object.create(baseSeguro).init();
2) Confundir in com “propriedade do objeto”
Para validações, in pode trazer propriedades herdadas (incluindo as injetadas na cadeia):
"toString" in {}; // true
Se o objetivo for validar apenas propriedades próprias, use Object.hasOwn() (mais moderno) ou hasOwnProperty:
const obj = Object.create(null);
obj.chave = 1;
console.log(Object.hasOwn(obj, "chave")); // true
3) Riscos de Prototype Pollution (visão de segurança)
Do ponto de vista de Cyber Segurança, a cadeia de protótipos pode ser explorada em certos cenários, especialmente quando aplicações mesclam objetos com entrada não confiável de forma ingênua. O problema mais conhecido é prototype pollution, onde chaves como __proto__, constructor e prototype podem alterar comportamentos inesperados ao se fazer merge de objetos.
Medidas práticas:
- Evite merges “profundos” (deep merge) caseiros sem filtragem de chaves perigosas.
- Prefira bibliotecas atualizadas e com proteção conhecida, ou implemente allowlist de chaves.
- Para mapas/dicionários de dados vindos do usuário, considere
Object.create(null)para evitar protótipo:
const dict = Object.create(null);
dict["__proto__"] = "valor"; // não altera o protótipo global
console.log(dict["__proto__"]); // "valor"
Isso não resolve todos os problemas de validação, mas reduz superfícies de ataque ligadas a herança via protótipo.
Quando usar Object.create() hoje
Cenários comuns e legítimos:
- Criar objetos simples com delegação clara (comportamento compartilhado).
- Implementar objetos “tipo dicionário” com
Object.create(null). - Controlar descritores de propriedades ao criar objetos.
- Evitar
newe reduzir acoplamento a funções construtoras em designs baseados em composição.
Em código moderno, class é frequentemente preferido por legibilidade e padronização, mas Object.create() segue sendo uma ferramenta direta e expressiva para modelar delegação.
Resumo: JavaScript Herança Classes sem classes
- A “herança” em JavaScript é delegação via prototype chain.
Object.create()cria objetos já conectados a um protótipo, semnew.- Funções construtoras com
prototypeforam o padrão dominante antes declass. - A sintaxe
classé uma camada mais confortável, mas o mecanismo continua sendo prototípico. - Em segurança, a cadeia de protótipos exige cuidado com merges de objetos e entradas não confiáveis (prototype pollution).
Entender esses fundamentos torna mais fácil depurar comportamento de objetos, ler código legado e tomar decisões mais seguras ao lidar com dados externos e estruturas de objetos complexas.