Todo objeto tem um prototype
JavaScript é uma linguagem baseada em protótipos. Parece algo exótico, mas a ideia é bem simples: todo objeto tem uma ligação secreta com outro objeto — o seu prototype — e, quando você pede uma propriedade que o objeto não tem, o JavaScript segue esse link e vai procurar lá.
O rabbit não tem uma propriedade eats própria. O JavaScript olha primeiro no rabbit, não encontra, segue o link do protótipo até animal, acha eats: true ali e retorna esse valor. Já no caso de flies, ele percorre a cadeia inteira, não encontra nada e devolve undefined.
Esse comportamento de buscar e subir na cadeia é todo o mecanismo. Herança, métodos, class — tudo isso é construído em cima disso.
A cadeia de protótipos no JavaScript
A cadeia não para em um único passo. Um protótipo pode ter seu próprio protótipo, e assim por diante, até chegar em null:
Rode o código e você vai ver rabbit, depois Object.prototype e, por fim, null. É por isso que rabbit.toString() funciona mesmo sem você ter definido toString — esse método mora em Object.prototype, que fica no topo de praticamente toda cadeia de protótipos.
A busca por propriedades percorre essa cadeia de baixo para cima. Já a atribuição é diferente: ela sempre escreve no próprio objeto, nunca sobe pela cadeia. Essa assimetria é importante e costuma pegar muita gente de surpresa.
Funções construtoras e .prototype
Antes de existir class, a forma padrão de criar vários objetos parecidos era usar uma função construtora chamada com new:
Duas coisas acontecem quando você chama new User("Ada"):
- Um objeto novo é criado, com seu protótipo apontando para
User.prototype. Useré executada comthisamarrado a esse novo objeto.
O greet não é copiado para cada instância. Ele vive em um único lugar: dentro de User.prototype. Tanto ada quanto boris o encontram percorrendo a cadeia de protótipos. É por isso que a última linha imprime true — trata-se literalmente da mesma função.
prototype vs __proto__
Esses dois nomes confundem todo mundo. Estão relacionados, mas não são a mesma coisa.
User.prototypeé uma propriedade da função construtora. É o objeto que se torna o protótipo das instâncias criadas comnew User(...).ada.__proto__(ouObject.getPrototypeOf(ada)) é o elo da instância que aponta para cima, até o seu protótipo.
Prefira Object.getPrototypeOf(obj) em vez de obj.__proto__ em código novo. O __proto__ é um acessor legado que foi mantido só por compatibilidade — quem é API oficial mesmo é a função.
Classes são só açúcar sintático sobre prototype
O JavaScript moderno permite escrever class, mas, por baixo dos panos, você continua lidando com protótipos. Compare as duas versões lado a lado:
greet foi parar em User.prototype, exatamente como aconteceria se você tivesse escrito tudo na mão. A palavra-chave class basicamente te entrega uma sintaxe mais limpa, regras mais rígidas (o new passa a ser obrigatório) e uma forma mais elegante de usar extends — mas o modelo em tempo de execução é idêntico.
Entender isso faz diferença na hora de ler mensagens de erro ou depurar o this. Um erro mencionando "User.prototype.greet" não é algum nome interno esquisito — é literalmente onde o método mora.
Herança nada mais é do que cadeias mais longas
O extends encadeia um prototype em outro. O prototype do pai vira o prototype do prototype do filho:
Buscar rex.eat percorre a cadeia rex → Dog.prototype → Animal.prototype, encontra eat ali e executa o método com this ainda apontando para rex. É só isso que o extends faz — ele monta a cadeia de protótipos pra você.
Criando objetos direto a partir de um protótipo
Nem precisa de construtor. O Object.create(proto) cria um novo objeto já com o protótipo que você indicar:
Sem class, sem new, sem função construtora. Dois objetos compartilhando um método através de um mesmo protótipo. Essa é a forma mais crua da herança prototípica em JavaScript — todo o resto é construído em cima disso.
hasOwnProperty: próprias vs herdadas
Como a busca percorre a cadeia de protótipos, "foo" in obj retorna true inclusive para propriedades herdadas. Quando você precisa saber se uma propriedade realmente pertence ao próprio objeto, use Object.hasOwn (ou o antigo hasOwnProperty):
name está na instância. greet está no protótipo. O in encontra os dois; já Object.hasOwn só acha o primeiro. Isso faz diferença sempre que você itera com for...in ou serializa um objeto — na maioria das vezes, você quer apenas as propriedades próprias.
Nunca faça monkey-patch em protótipos nativos
Como Array.prototype é compartilhado por todos os arrays do seu programa, você poderia adicionar métodos nele:
// Por favor, não faça isso.
Array.prototype.last = function () {
return this[this.length - 1];
};
[1, 2, 3].last(); // 3
O problema não é que não funcione — funciona, e muito bem. O problema é que toda biblioteca, toda dependência e toda versão futura do JavaScript passam a dividir esse mesmo namespace com você. No dia em que Array.prototype.last for lançado como método oficial, com uma semântica um pouco diferente, seu código (ou o de outra pessoa) quebra de um jeito sutil e difícil de rastrear. A saga do Array.prototype.flatten virando Array.prototype.flat é o exemplo clássico desse tipo de dor de cabeça.
Prefira manter helpers como funções independentes:
Uma superfície compartilhada a menos para dar dor de cabeça.
O modelo mental
Tirando todo o resto da frente, protótipos em JavaScript se resumem a três regras:
- Todo objeto tem um link de protótipo (que pode ser
null). - Leituras de propriedade sobem por essa cadeia; escritas não.
class,neweextendssão formas de montar essas cadeias sem você precisar escreverObject.createna mão.
Guarde essas três ideias e o comportamento de this, do instanceof, da resolução de métodos e da herança vai se encaixando naturalmente.
A seguir: o Event Loop
Com protótipos, fechamos o modelo de objetos. O próximo capítulo vai para um terreno totalmente diferente: como o JavaScript de fato executa seu código ao longo do tempo. O event loop é o que faz timers, promises e async/await se comportarem do jeito que se comportam — e é a base de tudo que é assíncrono.
Perguntas frequentes
O que é um prototype em JavaScript?
Todo objeto em JavaScript carrega um link interno para outro objeto, que é o seu prototype. Quando você acessa uma propriedade que não existe no próprio objeto, o JavaScript sobe por esse link — a chamada cadeia de protótipos — até encontrá-la. É justamente por causa dessa cadeia que métodos definidos uma única vez conseguem ser compartilhados entre várias instâncias.
Qual a diferença entre __proto__ e prototype?
prototype é uma propriedade que existe em funções construtoras (e em classes). Ele é o objeto que vai virar o protótipo das instâncias criadas com new. Já __proto__ (ou Object.getPrototypeOf(obj)) é o link real na instância, apontando para o protótipo dela. Ou seja: instance.__proto__ === Constructor.prototype.
Classes em JavaScript são só açúcar sintático para prototypes?
Na prática, quase tudo sim. class Foo { bar() {} } coloca bar em Foo.prototype, exatamente como se você tivesse escrito function Foo(){} e depois Foo.prototype.bar = function(){}. As classes ainda trazem campos privados, semântica mais rígida e uma sintaxe bem mais agradável para extends e super, mas por baixo dos panos continua sendo tudo protótipo.
Posso adicionar métodos em prototypes nativos como Array.prototype?
Melhor não. Mexer em Array.prototype ou Object.prototype afeta todo array ou objeto do seu programa, incluindo os que vêm de bibliotecas de terceiros. Isso pode conflitar com futuras adições da linguagem e quebrar laços for...in. Prefira deixar suas funções auxiliares em módulos ou funções próprias.