W3docs

Herencia Prototípica en JavaScript

Aprende la herencia prototípica en JavaScript: el enlace oculto [[Prototype]], __proto__ vs Object.getPrototypeOf/setPrototypeOf, la cadena de prototipos, F.prototype, sombreado de propiedades y cómo se enlaza this — con ejemplos ejecutables.

En JavaScript, los objetos pueden heredar propiedades y métodos de otros objetos a través de un mecanismo llamado herencia prototípica. En lugar de copiar características de una clase (como en Java o C++), cada objeto mantiene un enlace oculto hacia otro objeto — su prototipo — y JavaScript sigue ese enlace cuando no puede encontrar una propiedad en el propio objeto. Esta página explica cómo funciona ese enlace oculto, la diferencia entre __proto__ y Object.getPrototypeOf/setPrototypeOf, cómo se recorre la cadena de prototipos, el papel de F.prototype en las funciones constructoras, el sombreado de propiedades y cómo se comporta this a lo largo de la cadena.

El enlace oculto [[Prototype]]

Todo objeto JavaScript tiene una propiedad interna oculta llamada [[Prototype]]. Puede ser null o una referencia a otro objeto, y ese objeto referenciado se denomina prototipo del objeto.

[[Prototype]] es una ranura interna de la especificación del lenguaje — no se puede leer con un acceso normal a una propiedad. En cambio, se trabaja con ella mediante dos APIs públicas:

  • El accesor histórico __proto__ (un getter/setter expuesto por Object.prototype).
  • Los métodos modernos y recomendados Object.getPrototypeOf(obj) y Object.setPrototypeOf(obj, proto).

La forma más sencilla de definir un prototipo es con __proto__ dentro de un literal de objeto. Aquí hacemos que animal sea el prototipo de rabbit, de modo que rabbit pueda leer las propiedades de animal:


javascript— editable

__proto__ vs Object.getPrototypeOf / setPrototypeOf

__proto__ es un getter/setter que hace tiempo fue desaconsejado para uso general — solo está estandarizado por compatibilidad con navegadores. En código real, es preferible usar los métodos explícitos:


javascript— editable

Nótese la diferencia: __proto__ es un accesor de propiedad, mientras que getPrototypeOf/setPrototypeOf son funciones. Dos reglas prácticas:

  • __proto__ no es lo mismo que [[Prototype]]. __proto__ es simplemente el accesor que lee y escribe la ranura interna [[Prototype]].
  • Evita cambiar el prototipo de un objeto después de crearlo. Object.setPrototypeOf y la asignación de obj.__proto__ son operaciones lentas: los motores optimizan mucho los objetos cuyo prototipo se fija en el momento de la creación. Define el prototipo una sola vez, cuando construyes el objeto.

La cadena de prototipos

El prototipo de un prototipo también puede tener su propio prototipo, formando una cadena de prototipos. Cuando lees obj.prop, JavaScript:

  1. Busca prop como propiedad propia de obj.
  2. Si no la encuentra, sigue [[Prototype]] y busca en el prototipo.
  3. Repite el paso 2 subiendo por la cadena hasta encontrar prop o llegar a null.

Si la cadena termina en null sin encontrar la propiedad, el resultado es undefined.


javascript— editable

Existen dos limitaciones en la cadena: las referencias no deben formar un bucle (JavaScript lanza un error si intentas crear un ciclo) y [[Prototype]] debe ser un object o null.

Sombreado de propiedades: escritura vs lectura

El prototipo solo se consulta para leer. Cuando escribes o eliminas una propiedad, la operación siempre actúa sobre el propio objeto, nunca sobre su prototipo. Asignar una propiedad que también existe en el prototipo crea una propiedad propia que sombrea (oculta) la heredada:


javascript— editable

La excepción son las propiedades de acceso (getters/setters): como un setter es una llamada a función, escribir a través de un setter heredado ejecuta ese setter en lugar de crear una nueva propiedad de datos propia.

this siempre es el objeto que realiza la llamada

Una fuente habitual de confusión: sin importar dónde viva un método en la cadena, el valor de this dentro de él es el objeto antes del punto cuando se llamó al método — nunca el prototipo en el que fue definido. Los métodos heredados operan, por lo tanto, sobre el estado propio del objeto que hereda:


javascript— editable

Esto es lo que hace útiles los métodos compartidos en un prototipo: una sola definición de método, pero cada objeto guarda sus propios datos.

Funciones constructoras y F.prototype

Configurar [[Prototype]] manualmente para cada objeto resulta tedioso. El patrón clásico es una función constructora usada con new. Toda función tiene una propiedad regular llamada prototype (escrita como F.prototype). Cuando llamas a new F(), el [[Prototype]] del objeto recién creado se establece en F.prototype.


javascript— editable

Distingue F.prototype del [[Prototype]] de un objeto: F.prototype es una propiedad ordinaria de la función constructora que suministra el [[Prototype]] a los objetos creados con new F(). Por defecto, F.prototype es un objeto con una sola propiedad no enumerable constructor que apunta de vuelta a la función.


javascript— editable

Nota: La sintaxis moderna de class en ES6 es azúcar sintáctica sobre exactamente este mecanismo. Una declaración class crea una función constructora, coloca sus métodos en Constructor.prototype y conecta la cadena con los mismos enlaces [[Prototype]]. Consulta Herencia de Clases en JavaScript para ver la forma con extends/super.

Creación de objetos con Object.create

Object.create(proto) construye un nuevo objeto con [[Prototype]] establecido directamente en proto — sin necesidad de un constructor. Es la forma más explícita de configurar la herencia y acepta un segundo argumento: un mapa de descriptores de propiedad, en el mismo formato que usa Object.defineProperties.


javascript— editable

El mapa de descriptores permite controlar indicadores como writable, enumerable y configurable — consulta Indicadores y Descriptores de Propiedades en JavaScript para saber qué hace cada uno. Los objetos creados con Object.create(null) (los llamados objetos "muy planos") se tratan en Métodos de Prototipo, Objetos Sin __proto__.

Herencia multinivel

Como cada prototipo puede tener su propio prototipo, es posible construir cadenas de varios niveles de profundidad para modelar relaciones más específicas:


javascript— editable

Inspección e iteración de la cadena

Para comprobar si un objeto está en algún lugar de la cadena de otro objeto, usa isPrototypeOf. Para separar las propiedades propias de las heredadas, usa hasOwnProperty — ten en cuenta que for...in recorre toda la cadena (solo propiedades enumerables), mientras que Object.keys devuelve únicamente las claves propias.


javascript— editable

Los tipos integrados como arrays, funciones y fechas también se basan en esta cadena — sus métodos viven en Array.prototype, Function.prototype, etc. Consulta Prototipos Nativos de JavaScript para ver cómo están conectados los tipos integrados.

Resumen

  • Todo objeto tiene un [[Prototype]] oculto que es otro objeto o null.
  • Lee [[Prototype]] con Object.getPrototypeOf y establécelo con Object.setPrototypeOf; __proto__ es el accesor heredado y cambiar un prototipo tras la creación es lento.
  • Las lecturas recorren la cadena de prototipos hasta encontrar la propiedad o hasta que la cadena termina en null; las escrituras y eliminaciones siempre actúan sobre el propio objeto y pueden sombrear propiedades heredadas.
  • this dentro de un método es el objeto sobre el que se llamó al método, no el prototipo donde fue definido.
  • new F() establece el [[Prototype]] del nuevo objeto en F.prototype; class en ES6 es azúcar sintáctica sobre exactamente este mecanismo — consulta Herencia de Clases en JavaScript.

Práctica

Práctica
¿Qué afirmaciones describen con precisión la herencia prototípica en JavaScript?
¿Qué afirmaciones describen con precisión la herencia prototípica en JavaScript?
Was this page helpful?