Herencia prototípica en JavaScript.

La herencia prototípica es el sistema de delegación de objetos que define la esencia de JavaScript. A diferencia de otros lenguajes, aquí los objetos se vinculan directamente entre sí a través de una cadena de prototipos, permitiendo compartir métodos y propiedades de forma eficiente y dinámica sin necesidad de copiar código en memoria.

herencia prototipica en javascript

1. ¿Que es la herencia prototípica en JavaScript?

La herencia prototípica es el mecanismo por el cual un objeto puede acceder a propiedades y métodos de otro objeto a través de una cadena de prototipos.

En JavaScript:

  • No existen clases tradicionales como en Java o C++ (aunque hoy exista la sintaxis class).
  • Todo objeto puede heredar directamente de otro objeto.
  • Esa herencia ocurre a través de una propiedad interna llamada:
[[Prototype]]Lenguaje del código: JSON / JSON con comentarios (json)

🧠IDEA CLAVE:

Cuando intentás acceder a una propiedad:

obj.propLenguaje del código: CSS (css)

JavaScript hace esto internamente:

  1. Busca prop dentro de obj.
  2. Si no la encuentra…
  3. Va al objeto prototipo (obj.[[Prototype]]).
  4. Si tampoco está ahí…
  5. Sigue subiendo en la cadena de prototipos.
  6. Si llega a null, devuelve undefined.

Eso es la herencia prototípica.

1.1. Ejemplo — Herencia básica con Object.create():

Esta es la forma más pura de ver la herencia

// 1. Definimos un objeto base (el prototipo)
const mentorJS = {
  saludar: function() {
    console.log("¡Hola, futuro dev! Sigue practicando.");
  }
};

// 2. Creamos un nuevo objeto que HEREDA de mentorJS
const estudiante = Object.create(mentorJS);

// 3. Agregamos una propiedad propia al estudiante
estudiante.nombre = "Lucas";

// 4. Probamos la herencia
console.log(estudiante.nombre); // "Lucas" (Propiedad propia)
estudiante.saludar();           // "¡Hola, futuro dev!..." (Propiedad heredada)Lenguaje del código: JavaScript (javascript)

¿Qué pasa cuando ejecutamos estudiante.saludar();?

  • estudiante no tiene un método saludar en su propio cuerpo.
  • Cuando llamamos a estudiante.saludar(), el motor de JS busca en estudiante.
  • Al no encontrarlo, sube por la cadena de prototipos hacia mentorJS y lo ejecuta desde allí.

En otras palabras:

  1. ¿estudiante tiene saludar? ❌ No.
  2. Va a su prototipo (mentorJS ).
  3. ¿mentorJS tiene saludar? ✅ Sí.
  4. Lo ejecuta.

2. Entender el [[Prototype]] de un objeto y cómo se relaciona con __proto__

Estos conceptos son claves para entender a profundidad la herencia prototípica.

2.1. ¿Qué es [[Prototype]]? (El concepto)

Es una propiedad interna (u oculta) que tienen casi todos los objetos en JavaScript. Piensa en ella como un puntero o una dirección que apunta a otro objeto.

  • No puedes acceder a ella escribiendo objeto.[[Prototype]] en tu código (los corchetes dobles indican que es una propiedad interna del motor de JS).
  • Su única función es servir de guía: si buscas una propiedad en un objeto y no está, JS mira a dónde apunta su [[Prototype]].

2.2. ¿Qué es __proto__? (La herramienta)

Históricamente, los navegadores necesitaban una forma de que los desarrolladores pudieran ver o cambiar ese [[Prototype]] oculto. Así nació __proto__ (pronunciado «dunder proto»).

  1. Es un «getter/setter»:
    • No es el prototipo en sí, sino una vía de acceso para leer o modificar el [[Prototype]].
  2. Estado actual: Aunque casi todos los navegadores lo soportan, hoy se considera una mala práctica usarlo en código de producción. Lo correcto es usar:
    • Object.getPrototypeOf().
    • Object.setPrototypeOf().

Ejemplo para entender diferencia entre [[Prototype]] y __proto__

Vamos a crear un vínculo de herencia manualmente para ver cómo interactúan:

const programador = {
  lenguaje: "JavaScript",
  codear: function() {
    console.log(`Escribiendo código en ${this.lenguaje}...`);
  }
};

const luthierDev = {
  nombre: "Tu Nombre",
  // Queremos que luthierDev sepa codear como un programador
};

// Vinculamos luthierDev con programador usando __proto__
luthierDev.__proto__ = programador;
   // En otras palabras aquí hacemos que programador sea prototipo de luthierDev. 
   // Sería lo mismo que hicimos en el ejemplo anterior con Object.create(mentorJS); (Recomendado)

// 1. Acceso directo (Existe en el objeto)
console.log(luthierDev.nombre); // "Tu Nombre"

// 2. Acceso por herencia (No existe en luthierDev, busca en su [[Prototype]])
luthierDev.codear(); // "Escribiendo código en JavaScript..."

// 3. Verificación de la relación
console.log(luthierDev.__proto__ === programador); // trueLenguaje del código: JavaScript (javascript)

Explicación del flujo:

  1. Llamas a luthierDev.codear().
  2. JS busca dentro de luthierDev. No lo encuentra.
  3. JS dice: «A ver, ¿a dónde apunta el [[Prototype]] de este objeto?».
  4. Gracias a que asignamos __proto__, el puntero apunta a programador.
  5. JS encuentra codear en programador y lo ejecuta.

🚨 ANTERIORMENTE dijimos que actualmente es mejor usarObject.setPrototypeOf(obj, p) y Object.getPrototypeOf(obj).
Pero ¿Qué son exactamente?

2.3. Object.getPrototypeOf(obj)

Este método es el «lector de ADN». Sirve para preguntarle a un objeto: «¿Quién es tu prototipo (tu padre)?».

  • Para qué sirve: Para inspeccionar la cadena de herencia de un objeto de forma segura.
  • Por qué usarlo: Es más fiable que __proto__ y funciona correctamente incluso en objetos que no tienen un prototipo (objetos creados con Object.create(null)).

2.3.1. Ejemplo:

const animal = { comer: true };
const gato = Object.create(animal);

// Queremos saber de quién hereda 'gato'
const prototipoDeGato = Object.getPrototypeOf(gato);

console.log(prototipoDeGato === animal); // true
console.log(prototipoDeGato.comer);      // trueLenguaje del código: JavaScript (javascript)

Explicación: Object.getPrototypeOf(gato) nos devuelve el objeto animal. Es como mirar el mapa de conexiones que JavaScript tiene guardado internamente.

2.4. Object.setPrototypeOf(obj, nuevoProto)

Este método es el «trasplante de ADN». Sirve para cambiar el prototipo de un objeto que ya existe.

  • Para qué sirve: Para decirle a un objeto: «A partir de ahora, ya no heredas de ‘A’, ahora heredas de ‘B'».
  • Advertencia de profesor: Aunque existe, es un método muy costoso en rendimiento. Los motores de JavaScript (como V8 de Chrome) optimizan los objetos basándose en su estructura; si le cambias el prototipo «en caliente», el motor tiene que re-optimizar todo. Úsalo solo si es estrictamente necesario.

2.4.1. Ejemplo:

const programador = { sabeCodear: true };
const musico = { sabeTocarInstrumento: true };

const persona = { nombre: "Lucas" };

// Inicialmente, persona solo tiene su nombre
console.log(persona.sabeCodear); // undefined

// Cambiamos su "ADN" para que herede de 'programador'
Object.setPrototypeOf(persona, programador);

console.log(persona.sabeCodear); // true

// ¡Cambio de carrera! Ahora que herede de 'musico'
Object.setPrototypeOf(persona, musico);

console.log(persona.sabeCodear);           // undefined (ya no hereda de programador)
console.log(persona.sabeTocarInstrumento); // trueLenguaje del código: JavaScript (javascript)

Explicación: Al usar setPrototypeOf, desconectamos a persona de su prototipo anterior y lo conectamos a uno nuevo. Automáticamente, persona gana acceso a todas las propiedades del nuevo objeto.

3. Shadowing y relación con la herencia prototípica:

3.1. ¿Qué es el Shadowing?

El Shadowing (o «Sombreado de propiedades») es un concepto fundamental para entender cómo JavaScript decide qué valor mostrar cuando un objeto y su prototipo tienen una propiedad con el mismo nombre.

En otras palabras:

El shadowing ocurre cuando agregas una propiedad a un objeto que ya existe en su cadena de prototipos. Al hacerlo, la propiedad del objeto «oculta» o «sombra» a la del prototipo. La propiedad del prototipo no desaparece, simplemente queda inaccesible de forma directa a través de ese objeto hijo.

3.2. Ejemplo:

Imagina que tenemos un prototipo base y creamos un objeto a partir de él.

// 1. Definimos el prototipo (El "ADN" base)
const mentor = {
  lenguaje: "JavaScript",
  saludar: function() {
    console.log(`Hola, soy experto en ${this.lenguaje}`);
  }
};

// 2. Creamos al estudiante que hereda del mentor
const estudiante = Object.create(mentor);

// 3. Aplicamos SHADOWING
// El estudiante decide tener su propio lenguaje
estudiante.lenguaje = "TypeScript"; 

// 4. Probamos los resultados
console.log(estudiante.lenguaje); // "TypeScript" (Propiedad propia)
console.log(mentor.lenguaje);     // "JavaScript" (El prototipo sigue intacto)

estudiante.saludar(); // "Hola, soy experto en TypeScript"Lenguaje del código: JavaScript (javascript)

Explicación del proceso:

  1. Búsqueda:
    • Cuando pides estudiante.lenguaje, el motor de JS mira primero en el objeto estudiante.
  2. Encuentro inmediato:
    • Como definimos lenguaje directamente en el estudiante, JS lo usa y detiene la búsqueda.
  3. Sombreado (shadowing):
    • La propiedad lenguaje del objeto mentor quedó «en la sombra». JS ya no necesitó subir por la cadena de prototipos para encontrarla.
  4. Contexto (this):
    • Nota que al llamar a saludar(), el this apunta a estudiante, por lo que usa el valor «sombreado» (TypeScript).

3. La herencia prototípica en el DOM.

3.1. ¿Qué es la herencia prototípica en el DOM?

Es el mecanismo por el cual los elementos del DOM (como div, p, button, etc.) heredan propiedades y métodos desde objetos más generales a través de una cadena de prototipos.

Un elemento del DOM no tiene todos sus métodos propios, sino que los hereda desde niveles superiores como Element, Node o EventTarget.

Cuando hacés:

const div = document.createElement("div");Lenguaje del código: JavaScript (javascript)

Ese div no es un objeto simple.

Tiene una cadena de prototipos bastante grande.

🔎 Cadena real simplificada:

Un <div> hereda así:

divHTMLDivElement.prototypeHTMLElement.prototypeElement.prototypeNode.prototypeEventTarget.prototypeObject.prototypenullLenguaje del código: CSS (css)

Eso significa que div puede usar métodos que están definidos en cualquiera de esos niveles.

3.2. Ejemplo:

const div = document.createElement("div");div.addEventListener("click", () => {
  console.log("Click");
});Lenguaje del código: JavaScript (javascript)

Explicación:

El método addEventListener():

  • ❌ No está definido directamente en div
  • ❌ Tampoco en HTMLDivElement
  • ✅ Está definido en EventTarget de la cadena.

Pero como existe una cadena de prototipos, JavaScript lo encuentra subiendo niveles.

Cuando haces:

div.addEventListener(...)Lenguaje del código: CSS (css)

JavaScript:

  1. Busca en div
  2. No lo encuentra
  3. Sube por la cadena
  4. Lo encuentra en EventTarget
  5. Lo ejecuta

Eso es herencia prototípica aplicada al DOM.

🎯 IDEA A TENER EN MENTE:

Los elementos del DOM no copian métodos.
Los heredan mediante delegación en la cadena de prototipos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *