Prototipos en JavaScript: delegación y métodos compartidos | Nicolás Garzón
Un prototipo es un objeto al que otro objeto puede delegar la búsqueda de propiedades.
JavaScript
Copiar const baseUser = {
greet ( ) {
return ` Hello, ${ this . name} ` ;
} ,
} ;
const user = Object. create ( baseUser) ;
user. name = "Nicolás" ;
user. greet ( ) ; Los objetos normales tienen una propiedad interna llamada [[Prototype]] cuyo valor es otro objeto o null.
No se accede directamente con la sintaxis de corchetes. Las APIs recomendadas son:
JavaScript
Copiar Object. getPrototypeOf ( user) ;
Object. setPrototypeOf ( user, anotherPrototype) ; JavaScript
Copiar Object. getPrototypeOf ( user) === baseUser; Cambiar el prototipo de objetos existentes con Object.setPrototypeOf puede afectar rendimiento y volver el comportamiento difícil de rastrear. Normalmente se define durante la creación mediante Object.create, una función constructora o una clase.
JavaScript
Copiar user. __proto__; __proto__ es un accessor histórico heredado de Object.prototype. Permite leer o cambiar [[Prototype]] en muchos objetos, pero no es la API recomendada.
JavaScript
Copiar Object. getPrototypeOf ( user) ;
Object. setPrototypeOf ( user, prototype) ; Además, un objeto con prototipo null no hereda el accessor __proto__.
Una función constructible puede tener una propiedad normal llamada prototype.
JavaScript
Copiar function User ( name ) {
this . name = name;
}
User . prototype; Cuando se utiliza new User(), el objeto nuevo recibe como [[Prototype]] el valor de User.prototype.
JavaScript
Copiar const user = new User ( "Nicolás" ) ;
Object. getPrototypeOf ( user) === User . prototype;
User.prototype es una propiedad de la función.
Object.getPrototypeOf(user) obtiene el prototipo interno de la instancia.
Object.getPrototypeOf(User) obtiene el prototipo de la propia función, normalmente Function.prototype.
Las funciones normales suelen tener una propiedad prototype porque pueden ser constructibles.
JavaScript
Copiar function User ( ) { }
User . prototype; Las arrow functions no son constructibles y no tienen esa propiedad de la misma forma.
JavaScript
Copiar const User = ( ) => { } ;
User . prototype; Los métodos abreviados tampoco pueden utilizarse con new.
JavaScript
Copiar const service = {
create ( ) { } ,
} ;
new service. create ( ) ; Las clases sí tienen un objeto prototype para sus métodos de instancia.
JavaScript
Copiar function Product ( name, price ) {
this . name = name;
this . price = price;
}
Product . prototype. getLabel = function ( ) {
return ` ${ this . name} : $ ${ this . price} ` ;
} ; JavaScript
Copiar const keyboard = new Product ( "Keyboard" , 120 ) ;
const mouse = new Product ( "Mouse" , 60 ) ; JavaScript
Copiar keyboard. getLabel === mouse. getLabel; Las dos instancias encuentran la misma función en Product.prototype.
Sin prototipo compartido:
JavaScript
Copiar function Product ( name, price ) {
this . name = name;
this . price = price;
this . getLabel = function ( ) {
return ` ${ this . name} : $ ${ this . price} ` ;
} ;
} Cada instancia recibiría una función diferente.
JavaScript
Copiar user. greet;
Una propiedad propia greet en user.
Si no existe, en el prototipo de user.
Después en el prototipo de ese prototipo.
Continúa hasta llegar a null.
Si no la encuentra, devuelve undefined.
El recorrido completo se estudia en Cadena de prototipos .
JavaScript
Copiar const baseUser = {
greet ( ) {
return ` Hello, ${ this . name} ` ;
} ,
} ;
const user = Object. create ( baseUser) ;
user. name = "Nicolás" ;
user. greet ( ) ; Aunque el método se encontró en baseUser, this es user, porque user fue el receptor de la llamada.
Esto permite que varias instancias compartan un método y trabajen con sus propios datos.
Una propiedad propia puede ocultar otra heredada con el mismo nombre.
JavaScript
Copiar const defaults = {
theme : "light" ,
} ;
const settings = Object. create ( defaults) ;
settings. theme = "dark" ; JavaScript
Copiar settings. theme;
defaults. theme; La propiedad del prototipo no cambia.
Si se elimina la propiedad propia, vuelve a verse la heredada.
JavaScript
Copiar delete settings. theme;
settings. theme; La asignación no siempre crea una propiedad propia.
JavaScript
Copiar const base = {
set name ( value ) {
console. log ( ` Setting ${ value} ` ) ;
} ,
} ;
const user = Object. create ( base) ;
user. name = "Nicolás" ; JavaScript encuentra el setter heredado y lo ejecuta. Puede no crearse una propiedad propia name.
Para entender una escritura hay que considerar descriptores y la cadena de prototipos, no solo la línea de asignación.
JavaScript
Copiar const baseProduct = {
isAvailable ( ) {
return this . stock > 0 ;
} ,
} ;
const product = Object. create ( baseProduct) ;
product. name = "Keyboard" ;
product. stock = 4 ; También pueden definirse propiedades durante la creación.
JavaScript
Copiar const product = Object. create ( baseProduct, {
name : {
value : "Keyboard" ,
writable : true ,
enumerable : true ,
configurable : true ,
} ,
} ) ; Los descriptores omitidos utilizan false como valor por defecto.
JavaScript
Copiar const dictionary = Object. create ( null ) ; Este objeto no hereda de Object.prototype.
JavaScript
Copiar dictionary. toString; Puede ser útil como diccionario de claves externas porque no existen nombres heredados como constructor o toString.
No tiene métodos habituales de objeto.
Puede comportarse de forma distinta en herramientas.
Map suele expresar mejor un diccionario dinámico.
JavaScript
Copiar Object. hasOwn ( dictionary, "key" ) ; Object.hasOwn funciona aunque el objeto no tenga prototipo.
JavaScript
Copiar function User ( name ) {
this . name = name;
}
const user = new User ( "Nicolás" ) ;
User . prototype. greet = function ( ) {
return ` Hello, ${ this . name} ` ;
} ;
user. greet ( ) ; La instancia conserva una referencia al mismo objeto User.prototype.
Sin embargo, reemplazar el objeto completo es diferente.
JavaScript
Copiar const first = new User ( "First" ) ;
User . prototype = {
greet ( ) {
return ` Hi, ${ this . name} ` ;
} ,
} ;
const second = new User ( "Second" ) ; first mantiene el prototipo anterior y second recibe el nuevo.
JavaScript
Copiar Array . prototype. first = function ( ) {
return this [ 0 ] ;
} ; Esto se conoce frecuentemente como monkey patching y puede:
Chocar con nuevas APIs del lenguaje.
Alterar enumeraciones.
Romper librerías.
Cambiar el comportamiento global de todos los arrays.
Hacer difícil identificar el origen de un método.
Los polyfills legítimos requieren comprobaciones y compatibilidad cuidadosa. Para utilidades de aplicación, utiliza funciones normales.
JavaScript
Copiar function first ( array ) {
return array[ 0 ] ;
} Si una aplicación copia claves externas sin validarlas, un atacante puede intentar modificar prototipos mediante nombres especiales como __proto__, constructor o prototype.
JavaScript
Copiar function assignSettings ( target, input ) {
for ( const [ key, value] of Object. entries ( input) ) {
target[ key] = value;
}
} Con datos no confiables, esta operación necesita validación y una estrategia segura.
Aceptar únicamente claves conocidas.
Utilizar schemas de validación.
Evitar merges recursivos inseguros.
Usar objetos con prototipo null cuando corresponda.
Mantener dependencias actualizadas.
No basta con comprobar una sola clave; la defensa depende del flujo completo de datos.
JavaScript
Copiar Product . prototype. isPrototypeOf ( keyboard) ; JavaScript
Copiar keyboard instanceof Product ; instanceof consulta si Product.prototype aparece en la cadena de keyboard.
JavaScript
Copiar Object. getPrototypeOf ( keyboard) === Product . prototype; Cada técnica responde una pregunta ligeramente distinta.
Los prototipos permiten delegar comportamiento.
JavaScript
Copiar const printable = {
print ( ) {
return JSON . stringify ( this ) ;
} ,
} ; La composición crea objetos combinando capacidades explícitamente.
JavaScript
Copiar const product = {
... createIdentity ( ) ,
... createPricing ( ) ,
} ; No son enemigos. Puedes utilizar delegación prototípica, composición, closures y clases según el contrato del dominio.
JavaScript
Copiar const orderMethods = {
calculateTotal ( ) {
return this . items. reduce (
( total, item ) =>
total + item. price * item. quantity,
0 ,
) ;
} ,
complete ( ) {
this . status = "completed" ;
} ,
} ;
function createOrder ( id, items ) {
const order = Object. create ( orderMethods) ;
order. id = id;
order. items = [ ... items] ;
order. status = "pending" ;
return order;
} Todas las órdenes comparten orderMethods, pero mantienen propiedades propias.
Confundir prototype con [[Prototype]].
Utilizar __proto__ como API principal.
Decir que todas las funciones tienen un prototipo constructible.
Pensar que los métodos se copian en cada instancia.
Creer que un método heredado utiliza el prototipo como this.
Reemplazar un prototype y esperar que cambien instancias anteriores.
Modificar prototipos nativos para agregar utilidades.
Confundir cadena de prototipos con cadena de scopes.
Ignorar setters heredados durante una asignación.
Copiar claves externas sin considerar prototype pollution.
[[Prototype]] conecta un objeto con otro para delegar búsquedas.
Object.getPrototypeOf es la forma recomendada de consultarlo.
Constructor.prototype es la propiedad usada por new para las instancias.
Los métodos heredados se comparten, no se copian.
El receptor original sigue siendo this.
Una propiedad propia puede ocultar otra heredada.
Los objetos con prototipo null no heredan de Object.prototype.
Modificar prototipos compartidos afecta a objetos existentes.
No deben modificarse prototipos nativos sin una razón de compatibilidad muy controlada.
JavaScript
Copiar const base = {
name : "Base" ,
readName ( ) {
return this . name;
} ,
} ;
const child = Object. create ( base) ;
child. name = "Child" ;
console. log ( child. readName ( ) ) ; Respuesta Imprime "Child". El método se encuentra en base, pero la llamada tiene a child como receptor y, por tanto, como this.
Cadena de prototipos muestra cómo se conectan varios niveles y cómo instanceof, arrays, funciones y clases utilizan ese recorrido.