Métodos de Prototype em JavaScript Sem __proto__
Aprenda os métodos modernos de prototype do JavaScript — Object.create(), getPrototypeOf(), setPrototypeOf() — e como usar objects sem prototype como dicionários seguros.
Todo object JavaScript está vinculado a outro object chamado seu prototype, e as buscas de propriedades percorrem essa cadeia. A forma antiga de ler ou alterar esse vínculo era a propriedade especial __proto__, mas ela apresenta problemas reais: é um acessor herdado (não um slot de dado normal), comporta-se de forma inconsistente como chave de object e pode ser explorada para corromper objects. O JavaScript moderno a substitui por métodos explícitos e previsíveis no construtor Object.
Este capítulo apresenta os métodos padrão de prototype — Object.create(), Object.getPrototypeOf() e Object.setPrototypeOf() — e os helpers de inspeção Object.keys(), Object.values(), Object.entries() e Object.hasOwn(). Em seguida, aborda o caso de uso mais útil de um object sem prototype: um "dicionário" seguro para chaves arbitrárias.
Para entender melhor como a cadeia funciona, consulte Herança Prototípica e Além.
Lendo e Definindo um Prototype
Use Object.getPrototypeOf() para ler o prototype de um object e Object.setPrototypeOf() para alterá-lo. Esses são os substitutos padronizados para leitura e escrita de __proto__.
Object.create(proto) cria um object completamente novo cujo prototype é exatamente proto. É a forma mais limpa de criar um object com um prototype escolhido sem usar __proto__ de forma alguma.
Por Que Evitar __proto__
__proto__ é um getter/setter definido em Object.prototype, não uma propriedade que existe no seu object. Essa diferença causa dois problemas práticos:
- Pode falhar ou se comportar de forma estranha como chave de dado. Como
obj.__proto__ = valueaciona o setter, você não pode armazenar de forma confiável uma chave literalmente chamada"__proto__"em um object comum — atribuir um não-object é silenciosamente ignorado, e atribuir um object muda o prototype em vez de adicionar uma chave. - É uma superfície de ataque conhecida ("prototype pollution"). Código que copia chaves não confiáveis para um object pode ser induzido a escrever em
__proto__, poluindoObject.prototypepara todo o programa.
Os métodos padronizados são explícitos quanto à intenção: getPrototypeOf/setPrototypeOf alteram o prototype, enquanto escritas de propriedades normais nunca o tocam.
Objects Sem Prototype
Object.create(null) cria um object cujo prototype é null. Ele não herda nada — nem mesmo toString, hasOwnProperty, ou o acessor __proto__.
O Problema do Dicionário que Ele Resolve
Um padrão comum é usar um object simples como mapa de chaves string para valores. O problema é que um {} simples já herda chaves de Object.prototype, portanto chaves fornecidas pelo usuário como "constructor", "toString" ou "__proto__" colidem com os nomes herdados e quebram as buscas.
Um object com prototype null não possui chaves herdadas, portanto cada chave se comporta exatamente como escrita — incluindo "__proto__":
É por isso que objects com prototype null formam dicionários seguros para chaves não confiáveis. (O Map embutido é outra boa escolha e permite chaves não-string.)
Adicionando Métodos a um Object com Prototype Null
Como não há prototype para herdar, você atribui métodos diretamente como propriedades próprias.
Iterando Chaves, Valores e Entradas
Object.keys(), Object.values() e Object.entries() retornam arrays das propriedades enumeráveis próprias de um object. Crucialmente, eles ignoram a cadeia de prototypes, portanto funcionam de forma idêntica em objects normais e com prototype null. Consulte Object.keys, values, entries para mais detalhes.
Uma ressalva para objects com prototype null: eles não possuem toString herdado, portanto passá-los diretamente para template literals ou String() lança um erro. Itere com os helpers Object.* (acima) em vez de depender da conversão automática para string.
Verificando Propriedades Próprias com Object.hasOwn()
Para verificar se uma chave é propriedade própria do object (não herdada), prefira Object.hasOwn(). É o substituto moderno de obj.hasOwnProperty() e funciona mesmo em objects com prototype null, que não possuem o método hasOwnProperty de forma alguma.
Composição com Object.assign()
Quando você quer que um object adquira o comportamento de vários outros, a composição geralmente é mais clara do que uma cadeia de herança única: um object só pode ter um prototype, mas Object.assign(target, ...sources) pode incorporar métodos de muitas fontes copiando suas propriedades enumeráveis próprias para o destino.
Object.assign() também copia para um object com prototype null, fornecendo um dicionário composto sem superfície herdada:
Ressalva da cópia superficial: Object.assign() copia valores de propriedade, não clones profundos. Valores de object e array são copiados por referência, portanto a fonte e o destino compartilham os mesmos objects aninhados.
Para uma cópia profunda e independente, use structuredClone(source) em vez disso.
Boas Práticas
- Use os métodos padronizados, não
__proto__. Recorra aObject.create(),Object.getPrototypeOf()eObject.setPrototypeOf(). Trate__proto__como um acessor legado a ser evitado no seu próprio código. - Evite alterar prototypes após a criação. Definir um prototype escolhido no momento da criação com
Object.create(proto)é mais limpo e fácil de raciocinar do que mutá-lo depois comObject.setPrototypeOf(). - Use
Object.create(null)(ouMap) para dicionários de chaves não confiáveis. Isso remove chaves herdadas e previne surpresas de prototype pollution. - Prefira
Object.hasOwn()aobj.hasOwnProperty(). É mais curto, mais seguro de chamar e funciona em objects com prototype null. - Componha com
Object.assign()quando precisar de comportamento de múltiplas fontes — lembre-se apenas de que é uma cópia superficial.
Para material relacionado, consulte Métodos de object e "this" e Flags e descritores de propriedade.
Conclusão
Os métodos modernos Object.* de prototype oferecem controle explícito e previsível sobre a cadeia de prototypes, substituindo o acessor quirky __proto__. Object.create(null) produz um object limpo e livre de prototype que é ideal para dicionários, enquanto Object.keys/values/entries, Object.hasOwn() e Object.assign() permitem inspecionar e compor objects com segurança, independentemente do seu prototype.