Object.create: A Base da Herança Real
Object.create() é o método fundamental para criar herança real em JavaScript. Diferente de construtores tradicionais, ele permite criar um objeto com um protótipo específico, oferecendo controle total sobre a cadeia de protótipos. Esse método foi introduzido no ES5 e revolucionou como implementamos herança, sendo a base das práticas modernas.
const animal = {
fazer_som: function() {
return "Som genérico";
},
dormir: function() {
return "Zzzzz...";
}
};
const cachorro = Object.create(animal);
cachorro.fazer_som = function() {
return "Au au!";
};
cachorro.buscar = function() {
return "Trazendo a bolinha!";
};
console.log(cachorro.fazer_som()); // "Au au!"
console.log(cachorro.dormir()); // "Zzzzz..." (herdado)
console.log(cachorro.buscar()); // "Trazendo a bolinha!"
Note como cachorro herda dormir() sem tê-lo definido. A busca passa pela cadeia até encontrar em animal. Object.create() também aceita um segundo argumento com descritores de propriedade, permitindo configuração granular:
const veiculo = {
acelerar: function() { return "Acelerando..."; }
};
const carro = Object.create(veiculo, {
portas: {
value: 4,
writable: true,
enumerable: true
},
marca: {
value: "Toyota",
writable: false,
enumerable: true
}
});
console.log(carro.portas); // 4
console.log(carro.marca); // "Toyota"
carro.portas = 2; // Permitido
// carro.marca = "Honda"; // Erro em strict mode
Object.getPrototypeOf: Inspecionando a Cadeia
Object.getPrototypeOf() retorna o protótipo de um objeto, permitindo navegar e inspecionar a cadeia de protótipos. É essencial para debugging e para construir sistemas que dependem de metaprogramação. Ao contrário de __proto__, é a forma padronizada e recomendada.
const pessoa = {
saudar: function() { return "Olá!"; }
};
const joao = Object.create(pessoa);
joao.nome = "João";
const prototipo_joao = Object.getPrototypeOf(joao);
console.log(prototipo_joao === pessoa); // true
console.log(prototipo_joao.saudar()); // "Olá!"
// Navegar a cadeia completa
function mapear_cadeia(obj) {
let atual = obj;
let profundidade = 0;
while (atual !== null) {
console.log(`Nível ${profundidade}:`, Object.keys(atual));
atual = Object.getPrototypeOf(atual);
profundidade++;
}
}
mapear_cadeia(joao);
// Nível 0: [ 'nome' ]
// Nível 1: [ 'saudar' ]
// Nível 2: []
Combine getPrototypeOf() com Object.setPrototypeOf() para modificar a cadeia dinamicamente, embora com cuidado devido ao impacto em performance:
const funcionario = {
trabalhar: function() { return "Trabalhando..."; }
};
const gerente = {
liderar: function() { return "Liderando time..."; }
};
let carlos = Object.create(funcionario);
console.log(carlos.trabalhar()); // "Trabalhando..."
// Mudar protótipo dinamicamente
Object.setPrototypeOf(carlos, gerente);
console.log(carlos.liderar()); // "Liderando time..."
// carlos.trabalhar(); // Erro: não está mais na cadeia
Herança Real com Múltiplos Níveis
A verdadeira potência emerge ao criar hierarquias complexas que refletem relacionamentos reais do domínio. Diferente de herança de classe (que é sintetizada), aqui a herança é literal: objetos herdam diretamente de outros objetos.
const ser_vivo = {
respirar: function() { return "Respirando..."; },
reproduzir: function() { return "Reproduzindo..."; }
};
const animal_avancado = Object.create(ser_vivo);
animal_avancado.mover = function() { return "Movendo..."; };
animal_avancado.comer = function() { return "Comendo..."; };
const mamifero = Object.create(animal_avancado);
mamifero.amamentar = function() { return "Amamentando..."; };
const cao = Object.create(mamifero);
cao.latir = function() { return "Au au!"; };
cao.nome = "Rex";
// Verificar a cadeia
console.log(cao.latir()); // "Au au!"
console.log(cao.amamentar()); // "Amamentando..."
console.log(cao.comer()); // "Comendo..."
console.log(cao.respirar()); // "Respirando..."
// Verificar onde cada método vive
console.log(Object.getPrototypeOf(cao) === mamifero); // true
console.log(Object.getPrototypeOf(mamifero) === animal_avancado); // true
console.log(Object.getPrototypeOf(animal_avancado) === ser_vivo); // true
Padrão OLOO (Objects Linking to Objects)
Este padrão, popularizado por Kyle Simpson, evita construtor functions totalmente:
const controle_animal = {
init: function(nome, energia) {
this.nome = nome;
this.energia = energia;
return this;
},
gastar_energia: function(qtd) {
this.energia -= qtd;
return this.energia > 0 ? `${this.nome} tem ${this.energia}% de energia` : "Sem energia!";
}
};
const controle_passaro = Object.create(controle_animal);
controle_passaro.voar = function(km) {
return this.gastar_energia(km * 2) + " - Voando!";
};
const passaro = Object.create(controle_passaro).init("Tweety", 100);
console.log(passaro.voar(10)); // "Tweety tem 80% de energia - Voando!"
console.log(passaro.gastar_energia(20)); // "Tweety tem 60% de energia"
Conclusão
Dominar Object.create() e Object.getPrototypeOf() revela que a verdadeira herança em JavaScript não precisa de classes ou construtores complexos. Primeiro aprendizado: herança real é objetos ligados a objetos; a cadeia de protótipos é uma busca linear por propriedades. Segundo aprendizado: Object.create() oferece controle fino, desde descritores até criação de hierarquias limpas, enquanto getPrototypeOf() permite inspecionar e modificar essa relação. Terceiro aprendizado: padrões como OLOO demonstram que código mais legível e flexível emerge quando abandonamos a mentalidade de classes e abraçamos a composição de objetos.