CAP 13 · LEC 03·Conceptos profundos de JavaScript

Prototype chain: la herencia real de JavaScript

Las clases de JavaScript son syntactic sugar. Por debajo, todo se basa en prototype chain — una cadena de objetos enlazados. Entenderla te da el control real del lenguaje.

● AVANZADO10 min lectura3 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

Todo en JS tiene un prototype

Cada objeto en JavaScript tiene un vínculo interno a otro objeto llamado su prototype. Este vínculo crea una cadena que termina en Object.prototype — el progenitor de todo.

// Todos los objetos tienen prototype const persona = { nombre: "Ana" }; // Object.getPrototypeOf() — la forma correcta de leer el prototype console.log(Object.getPrototypeOf(persona) === Object.prototype); // true // Object.prototype es el final de la cadena console.log(Object.getPrototypeOf(Object.prototype)); // null // Los arrays también tienen cadena de prototypes const numeros = [1, 2, 3]; console.log(Object.getPrototypeOf(numeros) === Array.prototype); // true console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true // Por eso los arrays tienen .toString() aunque no lo defines tú: // numeros → Array.prototype → Object.prototype → null console.log(numeros.toString()); // "1,2,3" // hasOwnProperty — verificar si la propiedad es propia vs heredada console.log(persona.hasOwnProperty("nombre")); // true console.log(persona.hasOwnProperty("toString")); // false — heredada
interface Persona { nombre: string; } const persona: Persona = { nombre: "Ana" }; console.log(Object.getPrototypeOf(persona) === Object.prototype); // true console.log(Object.getPrototypeOf(Object.prototype)); // null const numeros: number[] = [1, 2, 3]; console.log(Object.getPrototypeOf(numeros) === Array.prototype); // true console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true console.log(numeros.toString()); // "1,2,3" // TypeScript sabe que hasOwnProperty existe gracias a Object.prototype console.log(persona.hasOwnProperty("nombre")); // true console.log(persona.hasOwnProperty("toString")); // false
Salidatrue null true true 1,2,3 true false

__proto__ vs prototype — instancia vs constructor

La confusión más común: __proto__ pertenece a las instancias; prototype pertenece a las funciones constructoras.

function Animal(nombre) { this.nombre = nombre; } // 'prototype' está en la función constructora Animal.prototype.hablar = function() { return `${this.nombre} hace un sonido`; }; const gato = new Animal("Michi"); // '__proto__' está en la instancia — apunta al prototype del constructor console.log(gato.__proto__ === Animal.prototype); // true console.log(Object.getPrototypeOf(gato) === Animal.prototype); // true (forma moderna) // La instancia tiene sus propias propiedades console.log(gato.hasOwnProperty("nombre")); // true console.log(gato.hasOwnProperty("hablar")); // false — está en el prototype console.log(gato.hablar()); // "Michi hace un sonido" // El objeto constructor también tiene su propio prototype console.log(typeof Animal.prototype); // "object" console.log(Animal.prototype.constructor === Animal); // true
class Animal { nombre: string; constructor(nombre: string) { this.nombre = nombre; } hablar(): string { return `${this.nombre} hace un sonido`; } } const gato = new Animal("Michi"); // TypeScript compila clases a funciones constructoras con prototypes console.log(Object.getPrototypeOf(gato) === Animal.prototype); // true console.log(gato.hasOwnProperty("nombre")); // true console.log(gato.hasOwnProperty("hablar")); // false — en prototype console.log(gato.hablar()); // "Michi hace un sonido" // El método 'hablar' está compartido entre todas las instancias const perro = new Animal("Fido"); console.log(Object.getPrototypeOf(gato) === Object.getPrototypeOf(perro)); // true
Salidatrue true true false Michi hace un sonido object true
Evita __proto__

__proto__ es una propiedad legacy. Usa Object.getPrototypeOf(obj) para leer y Object.setPrototypeOf(obj, proto) para cambiar el prototype. Mutar __proto__ directamente es una operación lenta y peligrosa.

Búsqueda en la cadena de prototype

Cuando accedes a una propiedad, JavaScript la busca en el objeto primero, luego sube por la cadena hasta encontrarla o llegar a null.

function Vehiculo(tipo) { this.tipo = tipo; } Vehiculo.prototype.describir = function() { return `Soy un ${this.tipo}`; }; Vehiculo.prototype.categoria = "transporte"; function Auto(marca, tipo) { Vehiculo.call(this, tipo); // llamar al constructor padre this.marca = marca; } // Establecer la cadena de prototype Auto.prototype = Object.create(Vehiculo.prototype); Auto.prototype.constructor = Auto; Auto.prototype.tocar_bocina = function() { return "¡Beep beep!"; }; const miAuto = new Auto("Toyota", "sedán"); // Búsqueda: miAuto → Auto.prototype → Vehiculo.prototype → Object.prototype → null console.log(miAuto.marca); // "Toyota" — propio console.log(miAuto.tocar_bocina()); // "¡Beep beep!" — en Auto.prototype console.log(miAuto.describir()); // "Soy un sedán" — en Vehiculo.prototype console.log(miAuto.categoria); // "transporte" — en Vehiculo.prototype console.log(miAuto.toString()); // en Object.prototype // instanceof recorre la cadena console.log(miAuto instanceof Auto); // true console.log(miAuto instanceof Vehiculo); // true
class Vehiculo { tipo: string; categoria: string = "transporte"; constructor(tipo: string) { this.tipo = tipo; } describir(): string { return `Soy un ${this.tipo}`; } } class Auto extends Vehiculo { marca: string; constructor(marca: string, tipo: string) { super(tipo); this.marca = marca; } tocarBocina(): string { return "¡Beep beep!"; } } const miAuto = new Auto("Toyota", "sedán"); console.log(miAuto.marca); // "Toyota" console.log(miAuto.tocarBocina()); // "¡Beep beep!" console.log(miAuto.describir()); // "Soy un sedán" console.log(miAuto.categoria); // "transporte" console.log(miAuto instanceof Auto); // true console.log(miAuto instanceof Vehiculo); // true
SalidaToyota ¡Beep beep! Soy un sedán transporte [object Object] true true

Object.create() — prototype explícito

Object.create(proto) crea un nuevo objeto con el prototype que tú especificas. Es la forma más directa y explícita de configurar la cadena.

// Objeto base con métodos compartidos const animalesBase = { respirar() { return `${this.nombre} respira`; }, dormir() { return `${this.nombre} duerme`; }, }; // Crear un objeto que herede de animalesBase const perro = Object.create(animalesBase); perro.nombre = "Fido"; perro.ladrar = function() { return `${this.nombre} dice: ¡Woof!`; }; console.log(perro.respirar()); // "Fido respira" — heredado console.log(perro.ladrar()); // "Fido dice: ¡Woof!" — propio // Object.create(null) — objeto SIN prototype (útil para diccionarios puros) const diccionario = Object.create(null); diccionario.clave = "valor"; // diccionario.hasOwnProperty // ❌ no existe — no tiene Object.prototype console.log("clave" in diccionario); // true // Verificar console.log(Object.getPrototypeOf(perro) === animalesBase); // true console.log(Object.getPrototypeOf(diccionario)); // null
interface AnimalBase { nombre: string; respirar(): string; dormir(): string; } const animalesBase: Omit<AnimalBase, "nombre"> & ThisType<AnimalBase> = { respirar() { return `${this.nombre} respira`; }, dormir() { return `${this.nombre} duerme`; }, }; interface Perro extends AnimalBase { ladrar(): string; } const perro = Object.create(animalesBase) as Perro; perro.nombre = "Fido"; perro.ladrar = function(): string { return `${this.nombre} dice: ¡Woof!`; }; console.log(perro.respirar()); // "Fido respira" console.log(perro.ladrar()); // "Fido dice: ¡Woof!" console.log(Object.getPrototypeOf(perro) === animalesBase); // true
SalidaFido respira Fido dice: ¡Woof! true null true

Las clases ES6 son syntactic sugar

Las clases de ES6 son una sintaxis más limpia sobre los mismos prototypes. El JavaScript generado es idéntico en comportamiento.

// Con clases ES6 (syntactic sugar) class Figura { constructor(color) { this.color = color; } describir() { return `Figura de color ${this.color}`; } } class Circulo extends Figura { constructor(color, radio) { super(color); this.radio = radio; } area() { return Math.PI * this.radio ** 2; } } const c = new Circulo("rojo", 5); console.log(c.describir()); // "Figura de color rojo" console.log(c.area().toFixed(2)); // "78.54" // Por debajo, ES5 equivalente: // Circulo.prototype es un objeto cuyo __proto__ es Figura.prototype console.log(Object.getPrototypeOf(Circulo.prototype) === Figura.prototype); // true console.log(c instanceof Figura); // true
class Figura { constructor(public color: string) {} describir(): string { return `Figura de color ${this.color}`; } } class Circulo extends Figura { constructor(color: string, public radio: number) { super(color); } area(): number { return Math.PI * this.radio ** 2; } } const c = new Circulo("rojo", 5); console.log(c.describir()); // "Figura de color rojo" console.log(c.area().toFixed(2)); // "78.54" // TypeScript compila class a funciones constructoras con prototypes // La cadena es idéntica a la versión manual console.log(Object.getPrototypeOf(Circulo.prototype) === Figura.prototype); // true console.log(c instanceof Figura); // true
SalidaFigura de color rojo 78.54 true true
Usa clases, entiende prototypes

En código moderno, usa la sintaxis de clases — es más legible y menos propensa a errores. Entiende los prototypes para debuggear comportamientos extraños, manipular objetos a bajo nivel y entender cómo funciona el lenguaje realmente.

Practica