Prototype e cadeia de protótipos
1. Fundamentos do Prototype em JavaScript
Em JavaScript, prototype é o mecanismo fundamental pelo qual objetos herdam propriedades e métodos de outros objetos. Diferentemente de linguagens baseadas em classes, JavaScript utiliza herança prototípica — cada objeto possui uma referência interna para outro objeto chamado de prototype.
Todo objeto em JavaScript possui um prototype oculto, acessível via Object.getPrototypeOf() ou pela propriedade __proto__ (depreciada, mas amplamente suportada):
const pessoa = { nome: 'Ana' };
console.log(Object.getPrototypeOf(pessoa)); // Object.prototype
console.log(pessoa.__proto__); // Object.prototype
console.log(pessoa.__proto__ === Object.prototype); // true
É crucial entender a diferença entre prototype (propriedade de funções construtoras) e [[Prototype]] (referência interna dos objetos). Funções possuem a propriedade prototype, que será atribuída como [[Prototype]] dos objetos criados com new:
function Animal(nome) {
this.nome = nome;
}
Animal.prototype.falar = function() {
return `${this.nome} faz algum som.`;
};
const cachorro = new Animal('Rex');
console.log(Object.getPrototypeOf(cachorro) === Animal.prototype); // true
2. Cadeia de Protótipos (Prototype Chain)
Quando acessamos uma propriedade em um objeto, o JavaScript primeiro verifica se ela existe no próprio objeto. Se não encontrar, sobe na cadeia de protótipos até encontrá-la ou chegar ao topo (null).
const avo = { nome: 'João' };
const pai = Object.create(avo);
pai.sobrenome = 'Silva';
const filho = Object.create(pai);
filho.idade = 25;
console.log(filho.idade); // 25 (própria)
console.log(filho.sobrenome); // 'Silva' (do pai)
console.log(filho.nome); // 'João' (do avô)
console.log(filho.toString); // função (de Object.prototype)
// Verificando a cadeia
console.log(filho instanceof Object); // true
console.log(pai.isPrototypeOf(filho)); // true
O topo da cadeia é Object.prototype, que por sua vez tem null como prototype. Isso encerra a busca.
3. Criando Objetos com Prototypes
Existem três formas principais de definir o prototype de um objeto:
Funções construtoras:
function Carro(marca) {
this.marca = marca;
}
Carro.prototype.ligar = function() {
return `${this.marca} ligou.`;
};
const fusca = new Carro('Volkswagen');
Object.create():
const veiculo = {
mover() { return 'Movendo...'; }
};
const moto = Object.create(veiculo);
moto.tipo = 'esportiva';
console.log(moto.mover()); // 'Movendo...'
Literais de objeto:
const obj = {}; // prototype = Object.prototype
A diferença fundamental: new executa a função construtora e vincula o prototype; Object.create() apenas cria o objeto com o prototype especificado; literais sempre herdam de Object.prototype.
4. Herança via Prototype
Podemos implementar herança configurando manualmente a cadeia de protótipos:
function Animal(nome) {
this.nome = nome;
}
Animal.prototype.comer = function() {
return `${this.nome} está comendo.`;
};
function Cachorro(nome, raca) {
Animal.call(this, nome); // herda propriedades
this.raca = raca;
}
// Configurando herança
Cachorro.prototype = Object.create(Animal.prototype);
Cachorro.prototype.constructor = Cachorro; // corrige o construtor
// Sobrescrita (shadowing)
Cachorro.prototype.comer = function() {
return `${this.nome} (${this.raca}) está comendo ração.`;
};
const rex = new Cachorro('Rex', 'Pastor Alemão');
console.log(rex.comer()); // 'Rex (Pastor Alemão) está comendo ração.'
O problema do constructor ocorre porque Object.create() sobrescreve o prototype, perdendo a referência correta. A linha Cachorro.prototype.constructor = Cachorro resolve isso.
5. Prototype vs Classes ES6
As classes ES6 são açúcar sintático sobre o sistema de protótipos:
// Classe ES6
class Pessoa {
constructor(nome) {
this.nome = nome;
}
saudacao() {
return `Olá, sou ${this.nome}`;
}
static criar(nome) {
return new Pessoa(nome);
}
}
// Equivalente com prototype
function PessoaOld(nome) {
this.nome = nome;
}
PessoaOld.prototype.saudacao = function() {
return `Olá, sou ${this.nome}`;
};
PessoaOld.criar = function(nome) {
return new PessoaOld(nome);
};
Herança com extends e super:
class Funcionario extends Pessoa {
constructor(nome, cargo) {
super(nome); // chama construtor da classe pai
this.cargo = cargo;
}
saudacao() {
return `${super.saudacao()} e trabalho como ${this.cargo}`;
}
}
Por baixo dos panos, extends configura a cadeia de protótipos exatamente como vimos na seção anterior.
6. Boas Práticas e Performance
Adicionar métodos ao prototype economiza memória, pois todas as instâncias compartilham a mesma função:
// Ruim: cada instância tem sua própria função
function Ruim() {
this.metodo = function() { /* ... */ };
}
// Bom: todas as instâncias compartilham
function Bom() {}
Bom.prototype.metodo = function() { /* ... */ };
Evite mutar prototypes de objetos nativos em tempo de execução (como Array.prototype.meuMetodo = ...), pois isso pode causar conflitos em bibliotecas e quebrar a previsibilidade do código.
7. Prototype no Contexto Node.js e React
Node.js: O módulo EventEmitter utiliza prototypes extensivamente:
const EventEmitter = require('events');
class MeuEmissor extends EventEmitter {
constructor() {
super();
}
disparar() {
this.emit('evento', { dados: 'exemplo' });
}
}
// Equivalente prototípico:
function MeuEmissorOld() {
EventEmitter.call(this);
}
MeuEmissorOld.prototype = Object.create(EventEmitter.prototype);
MeuEmissorOld.prototype.disparar = function() {
this.emit('evento', { dados: 'exemplo' });
};
React: Antes dos hooks, componentes de classe usavam prototypes para métodos de ciclo de vida:
class MeuComponente extends React.Component {
constructor(props) {
super(props);
this.state = { contador: 0 };
}
incrementar() {
this.setState({ contador: this.state.contador + 1 });
}
render() {
return <button onClick={() => this.incrementar()}>{this.state.contador}</button>;
}
}
Hoje, componentes funcionais com hooks são preferíveis, mas o entendimento de prototypes ainda é essencial para depurar código legado e compreender o funcionamento interno de bibliotecas como Redux e React Router.
Referências
- MDN Web Docs: Herança e cadeia de protótipos — Documentação oficial explicando o mecanismo de herança prototípica em JavaScript.
- Node.js Documentation: EventEmitter — Documentação oficial do módulo EventEmitter, que utiliza prototypes extensivamente.
- JavaScript.info: Prototype inheritance — Tutorial completo sobre herança prototípica com exemplos práticos e exercícios.
- React Legacy Docs: Componentes de Classe — Documentação oficial de componentes de classe React, demonstrando uso de prototypes.
- You Don't Know JS: this & Object Prototypes — Livro técnico aprofundado sobre prototypes e cadeia de protótipos por Kyle Simpson.
- V8 Blog: Fast properties in V8 — Artigo técnico sobre como o motor V8 otimiza propriedades e prototypes em tempo de execução.