W3docs

Herança Prototípica em JavaScript

Aprenda herança prototípica em JavaScript: o link [[Prototype]], __proto__ vs Object.getPrototypeOf/setPrototypeOf, cadeia de protótipos, F.prototype, sombreamento de propriedades e como this se comporta na cadeia.

Em JavaScript, objects podem herdar propriedades e métodos de outros objects por meio de um mecanismo chamado herança prototípica. Em vez de copiar funcionalidades de uma classe (como em Java ou C++), cada object mantém um link oculto para outro object — seu protótipo — e o JavaScript segue esse link sempre que não encontra uma propriedade no próprio object. Esta página aborda como esse link oculto funciona, a diferença entre __proto__ e Object.getPrototypeOf/setPrototypeOf, como a cadeia de protótipos é percorrida, o papel de F.prototype para funções construtoras, sombreamento de propriedades e como this se comporta ao longo da cadeia.

Todo object JavaScript possui uma propriedade interna oculta chamada [[Prototype]]. Ela é null ou uma referência a outro object, e esse object referenciado é chamado de protótipo do object.

[[Prototype]] é um slot interno da especificação da linguagem — não é possível lê-lo com um acesso normal a propriedade. Em vez disso, você trabalha com ele por meio de duas APIs públicas:

  • O acessor histórico __proto__ (um getter/setter exposto por Object.prototype).
  • Os métodos modernos e recomendados Object.getPrototypeOf(obj) e Object.setPrototypeOf(obj, proto).

A maneira mais simples de definir um protótipo é com __proto__ dentro de um object literal. Aqui fazemos animal ser o protótipo de rabbit, para que rabbit possa ler as propriedades de animal:


javascript— editable

__proto__ vs Object.getPrototypeOf / setPrototypeOf

__proto__ é um getter/setter que há muito tempo está depreciado para uso geral — ele é padronizado apenas para compatibilidade com navegadores. Em código real, prefira os métodos explícitos:


javascript— editable

Note a diferença: __proto__ é um acessor de propriedade, enquanto getPrototypeOf/setPrototypeOf são funções. Duas regras práticas:

  • __proto__ não é o mesmo que [[Prototype]]. __proto__ é apenas o acessor que lê/escreve o slot interno [[Prototype]].
  • Evite alterar um protótipo após a criação de um object. Object.setPrototypeOf e a atribuição de obj.__proto__ são operações lentas: os mecanismos de JavaScript otimizam fortemente objects cujo protótipo é fixado no momento da criação. Defina o protótipo uma única vez, ao construir o object.

A cadeia de protótipos

O protótipo de um protótipo também pode ter um protótipo, formando uma cadeia de protótipos. Quando você lê obj.prop, o JavaScript:

  1. Procura prop como propriedade própria de obj.
  2. Se não encontrar, segue [[Prototype]] e procura no protótipo.
  3. Repete o passo 2 subindo na cadeia até encontrar prop ou chegar a null.

Se a cadeia terminar em null sem encontrar a propriedade, o resultado é undefined.


javascript— editable

Há dois limites na cadeia: as referências não podem formar um loop (o JavaScript lança um erro se você tentar criar um ciclo), e [[Prototype]] deve ser um object ou null.

Sombreamento de propriedades: escrita vs leitura

O protótipo é consultado apenas para leitura. Quando você escreve ou exclui uma propriedade, a operação sempre age sobre o próprio object, nunca sobre seu protótipo. Atribuir uma propriedade que também existe no protótipo cria uma propriedade própria que sombreia (oculta) a herdada:


javascript— editable

A exceção são as propriedades acessoras (getters/setters): como um setter é uma chamada de função, escrever por meio de um setter herdado executa esse setter em vez de criar uma nova propriedade de dados própria.

this é sempre o object que faz a chamada

Uma fonte comum de confusão: não importa onde um método esteja na cadeia, o this dentro dele é o object antes do ponto no momento em que o método foi chamado — nunca o protótipo onde ele foi definido. Métodos herdados, portanto, operam sobre o estado próprio do object herdeiro:


javascript— editable

É isso que torna os métodos compartilhados em um protótipo úteis: uma única definição de método, mas cada object armazena seus próprios dados.

Funções construtoras e F.prototype

Definir [[Prototype]] manualmente para cada object é tedioso. O padrão clássico é usar uma função construtora com new. Toda função tem uma propriedade comum chamada prototype (escrita como F.prototype). Quando você chama new F(), o [[Prototype]] do object recém-criado é definido como F.prototype.


javascript— editable

Mantenha F.prototype distinto do [[Prototype]] de um object: F.prototype é uma propriedade comum da função construtora que fornece o [[Prototype]] para objects criados com new F(). Por padrão, F.prototype é um object com uma única propriedade não enumerável constructor que aponta de volta para a função em si.


javascript— editable

Nota: A sintaxe class do ES6 moderno é açúcar sintático sobre exatamente esse mecanismo. Uma declaração class cria uma função construtora, coloca seus métodos em Constructor.prototype e conecta a cadeia com os mesmos links [[Prototype]]. Veja Herança de Classes em JavaScript para a forma com extends/super.

Criando objects com Object.create

Object.create(proto) constrói um object novo com [[Prototype]] definido diretamente como proto — sem necessidade de construtor. É a maneira mais explícita de configurar herança, e aceita um segundo argumento: um mapa de descritores de propriedades, no mesmo formato usado por Object.defineProperties.


javascript— editable

O mapa de descritores permite controlar flags como writable, enumerable e configurable — veja Flags e Descritores de Propriedades em JavaScript para saber o que cada flag faz. Objects criados com Object.create(null) (os chamados objects "muito simples") são abordados em Métodos de Protótipo, Objects Sem __proto__.

Herança multinível

Como cada protótipo pode ter seu próprio protótipo, você pode construir cadeias com vários níveis de profundidade para modelar relacionamentos mais específicos:


javascript— editable

Inspecionando e iterando a cadeia

Para verificar se um object está em algum lugar da cadeia de outro object, use isPrototypeOf. Para separar propriedades próprias das herdadas, use hasOwnProperty — note que for...in percorre toda a cadeia (somente propriedades enumeráveis), enquanto Object.keys retorna apenas as chaves próprias.


javascript— editable

Tipos embutidos como arrays, funções e datas também dependem dessa cadeia — seus métodos residem em Array.prototype, Function.prototype e assim por diante. Veja Protótipos Nativos em JavaScript para saber como os tipos embutidos estão conectados.

Resumo

  • Todo object possui um [[Prototype]] oculto que é outro object ou null.
  • Leia [[Prototype]] com Object.getPrototypeOf, defina-o com Object.setPrototypeOf; __proto__ é o acessor legado e alterar um protótipo após a criação é lento.
  • As leituras percorrem a cadeia de protótipos até a propriedade ser encontrada ou a cadeia terminar em null; escritas e exclusões sempre agem sobre o próprio object e podem sombrear propriedades herdadas.
  • this dentro de um método é o object no qual o método foi chamado, não o protótipo onde ele foi definido.
  • new F() define o [[Prototype]] do novo object como F.prototype; o class do ES6 é açúcar sintático sobre exatamente esse mecanismo — veja Herança de Classes em JavaScript.

Prática

Prática
Quais afirmações descrevem corretamente a Herança Prototípica em JavaScript?
Quais afirmações descrevem corretamente a Herança Prototípica em JavaScript?
Was this page helpful?