Métodos de prototipo de JavaScript sin __proto__
Aprende los métodos modernos de prototipo en JavaScript: Object.create(), getPrototypeOf(), setPrototypeOf(), hasOwn() y objetos sin prototipo como diccionarios seguros.
Todo object de JavaScript está vinculado a otro object llamado su prototipo, y las búsquedas de propiedades recorren esa cadena. La forma antigua de leer o modificar ese vínculo era la propiedad especial __proto__, pero tiene problemas reales: es un accessor heredado (no una ranura de datos normal), se comporta de manera inconsistente como clave de object y puede ser explotada para corromper objetos. El JavaScript moderno la reemplaza con métodos explícitos y predecibles en el constructor Object.
Este capítulo muestra los métodos de prototipo estándar — Object.create(), Object.getPrototypeOf() y Object.setPrototypeOf() — y los ayudantes de inspección Object.keys(), Object.values(), Object.entries() y Object.hasOwn(). Luego cubre el caso más útil de un object sin prototipo: un "diccionario" seguro para claves arbitrarias.
Para una visión más amplia de cómo funciona la cadena, consulta Herencia prototípica y más allá.
Leer y establecer un prototipo
Usa Object.getPrototypeOf() para leer el prototipo de un object y Object.setPrototypeOf() para cambiarlo. Estos son los reemplazos estandarizados para leer y escribir __proto__.
Object.create(proto) construye un object completamente nuevo cuyo prototipo es exactamente proto. Es la forma más limpia de crear un object con un prototipo elegido sin usar __proto__ en absoluto.
Por qué evitar __proto__
__proto__ es un getter/setter definido en Object.prototype, no una propiedad que vive en tu object. Esa diferencia causa dos problemas prácticos:
- Puede fallar o comportarse de forma extraña como clave de datos. Debido a que
obj.__proto__ = valueactiva el setter, no puedes almacenar de forma confiable una clave literalmente llamada"__proto__"en un object ordinario — asignar un valor que no sea un object se ignora silenciosamente, y asignar un object cambia el prototipo en lugar de agregar una clave. - Es una superficie de ataque conocida ("contaminación de prototipo"). El código que copia claves no confiables sobre un object puede ser engañado para escribir en
__proto__, contaminandoObject.prototypepara todo el programa.
Los métodos estandarizados son explícitos en cuanto a su intención: getPrototypeOf/setPrototypeOf cambian el prototipo, mientras que las escrituras normales de propiedades nunca lo tocan.
Objetos sin prototipo
Object.create(null) crea un object cuyo prototipo es null. No hereda nada — ni siquiera toString, hasOwnProperty ni el accessor __proto__.
El problema de diccionario que resuelve
Un patrón común es usar un object plano como mapa de claves string a valores. El problema es que un {} plano ya hereda claves de Object.prototype, por lo que las claves suministradas por el usuario como "constructor", "toString" o "__proto__" colisionan con los nombres heredados y rompen las búsquedas.
Un object con prototipo null no tiene claves heredadas, por lo que cada clave se comporta exactamente como se escribió — incluyendo "__proto__":
Por eso los objetos con prototipo null son diccionarios seguros para claves no confiables. (El Map incorporado es otra buena opción y permite claves que no sean string.)
Agregar métodos a un object con prototipo null
Como no hay prototipo del cual heredar, asignas métodos directamente como propiedades propias.
Iterar claves, valores y entradas
Object.keys(), Object.values() y Object.entries() devuelven arrays de las propiedades enumerables propias de un object. Crucialmente, ignoran la cadena de prototipos, por lo que funcionan de manera idéntica en objetos normales y objetos con prototipo null. Consulta Object.keys, values, entries para más información.
Una advertencia para los objetos con prototipo null: no tienen toString heredado, por lo que pasar uno directamente a template literals o String() lanza un error. Itera con los helpers Object.* (mostrados arriba) en lugar de depender de la conversión automática a string.
Verificar propiedades propias con Object.hasOwn()
Para comprobar si una clave es propiedad propia del object (no heredada), prefiere Object.hasOwn(). Es el reemplazo moderno de obj.hasOwnProperty() y funciona incluso en objetos con prototipo null, que no tienen ningún método hasOwnProperty.
Composición con Object.assign()
Cuando quieres que un object adquiera el comportamiento de varios otros, la composición suele ser más clara que una sola cadena de herencia: un object solo puede tener un prototipo, pero Object.assign(target, ...sources) puede incorporar métodos de muchas fuentes copiando sus propiedades enumerables propias en el destino.
Object.assign() también copia sobre un object con prototipo null, dándote un diccionario compuesto sin superficie heredada:
Advertencia de copia superficial: Object.assign() copia los valores de las propiedades, no clones profundos. Los valores de object y array se copian por referencia, por lo que el origen y el destino comparten los mismos objetos anidados.
Para una copia profunda e independiente, usa structuredClone(source) en su lugar.
Buenas prácticas
- Usa los métodos estandarizados, no
__proto__. Recurre aObject.create(),Object.getPrototypeOf()yObject.setPrototypeOf(). Trata__proto__como un accessor heredado que debes evitar en tu propio código. - Evita cambiar prototipos después de la creación. Establecer un prototipo elegido en el momento de la creación con
Object.create(proto)es más limpio y fácil de razonar que mutarlo después conObject.setPrototypeOf(). - Usa
Object.create(null)(oMap) para diccionarios de claves no confiables. Elimina las claves heredadas y previene sorpresas de contaminación de prototipo. - Prefiere
Object.hasOwn()sobreobj.hasOwnProperty(). Es más corto, más seguro de llamar y funciona en objetos con prototipo null. - Compón con
Object.assign()cuando necesites comportamiento de múltiples fuentes — solo recuerda que es una copia superficial.
Para material relacionado, consulta Métodos de object y "this" y Indicadores y descriptores de propiedades.
Conclusión
Los métodos de prototipo modernos de Object.* te brindan un control explícito y predecible sobre la cadena de prototipos, reemplazando el peculiar accessor __proto__. Object.create(null) produce un object limpio y libre de prototipo que es ideal para diccionarios, mientras que Object.keys/values/entries, Object.hasOwn() y Object.assign() te permiten inspeccionar y componer objetos de forma segura independientemente de su prototipo.