Prototipos y Herencia
Cadena de prototipos, herencia prototípica y cómo funciona la herencia de JavaScript bajo el capó
Comprender los Prototipos de JavaScript
JavaScript usa herencia prototípica en lugar de herencia clásica. Cada objeto tiene un prototipo interno del cual hereda propiedades y métodos. Comprender los prototipos es crucial para dominar la programación orientada a objetos en JavaScript.
Conceptos Clave
- [[Prototype]] — Enlace interno al prototipo del objeto
- __proto__ — Propiedad accessor para [[Prototype]] (no usar en producción)
- prototype — Propiedad de las funciones constructoras
- Cadena de prototipos — Series de objetos enlazados
La Cadena de Prototipos
// Cada objeto tiene un prototipo
const objeto = { a: 1 };
// objeto → Object.prototype → null
// Accediendo al prototipo
console.log(Object.getPrototypeOf(objeto)); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Búsqueda en la cadena de prototipos
const persona = {
nombre: "Alice",
saludar() {
return `Hola, soy ${this.nombre}`;
}
};
const empleado = Object.create(persona);
empleado.cargo = "Desarrollador";
console.log(empleado.cargo); // "Desarrollador" (propia propiedad)
console.log(empleado.nombre); // "Alice" (heredado del prototipo)
console.log(empleado.saludar()); // "Hola, soy undefined" (this se refiere a empleado)
// Cadena de búsqueda:
// 1. Buscar en empleado
// 2. Buscar en persona
// 3. Buscar en Object.prototype
// 4. Retornar undefined si no se encuentra
Funciones Constructoras
// Función constructora (convención: mayúscula)
function Persona(nombre, edad) {
// Propiedades de instancia
this.nombre = nombre;
this.edad = edad;
}
// Métodos compartidos en el prototipo
Persona.prototype.saludar = function() {
return `Hola, soy ${this.nombre}, tengo ${this.edad} años`;
};
Persona.prototype.cumpleanios = function() {
this.edad++;
return this.edad;
};
// Crear instancias
const alice = new Persona("Alice", 30);
const bob = new Persona("Bob", 25);
console.log(alice.saludar()); // "Hola, soy Alice, tengo 30 años"
alice.cumpleanios();
console.log(alice.edad); // 31
// Instancias comparten el mismo prototipo
console.log(alice.saludar === bob.saludar); // true (mismo método)
console.log(alice.nombre === bob.nombre); // false (diferentes propiedades)
Object.create()
// Crear objeto con prototipo específico
const animal = {
tipo: "Animal",
hacerSonido() {
return "Algún sonido";
}
};
// perro hereda de animal
const perro = Object.create(animal);
perro.nombre = "Rex";
perro.hacerSonido = function() {
return "Guau!";
};
console.log(perro.nombre); // "Rex" (propia)
console.log(perro.tipo); // "Animal" (heredado)
console.log(perro.hacerSonido()); // "Guau!" (sobrescrito)
// Verificar prototipo
console.log(Object.getPrototypeOf(perro) === animal); // true
// Crear objeto sin prototipo
const objetoSinProto = Object.create(null);
// No tiene Object.prototype, útil para diccionarios
console.log(objetoSinProto.toString); // undefined
Herencia Prototípica
// Constructor padre
function Animal(nombre) {
this.nombre = nombre;
}
Animal.prototype.comer = function() {
return `${this.nombre} está comiendo`;
};
// Constructor hijo
function Perro(nombre, raza) {
// Llamar al constructor padre
Animal.call(this, nombre);
this.raza = raza;
}
// Configurar herencia
Perro.prototype = Object.create(Animal.prototype);
Perro.prototype.constructor = Perro;
// Agregar métodos específicos de Perro
Perro.prototype.ladrar = function() {
return `${this.nombre} dice Guau!`;
};
// Sobrescribir método
Perro.prototype.comer = function() {
return `${this.nombre} come comida de perro`;
};
const rex = new Perro("Rex", "Pastor Alemán");
console.log(rex.nombre); // "Rex"
console.log(rex.raza); // "Pastor Alemán"
console.log(rex.comer()); // "Rex come comida de perro"
console.log(rex.ladrar()); // "Rex dice Guau!"
// Verificar cadena de prototipos
console.log(rex instanceof Perro); // true
console.log(rex instanceof Animal); // true
console.log(rex instanceof Object); // true
Sintaxis de Clases ES6
// Las clases son azúcar sintáctico sobre prototipos
class Persona {
constructor(nombre, edad) {
this.nombre = nombre;
this.edad = edad;
}
saludar() {
return `Hola, soy ${this.nombre}`;
}
// Método estático
static especie() {
return "Homo sapiens";
}
}
class Empleado extends Persona {
constructor(nombre, edad, cargo) {
super(nombre, edad); // Llamar al constructor padre
this.cargo = cargo;
}
// Sobrescribir método
saludar() {
return `${super.saludar()}, trabajo como ${this.cargo}`;
}
trabajar() {
return `${this.nombre} está trabajando`;
}
}
const emp = new Empleado("Alice", 30, "Desarrollador");
console.log(emp.saludar()); // "Hola, soy Alice, trabajo como Desarrollador"
console.log(emp.trabajar()); // "Alice está trabajando"
console.log(Persona.especie()); // "Homo sapiens"
// Todavía es herencia prototípica bajo el capó
console.log(emp.__proto__ === Empleado.prototype); // true
Métodos de Prototipo Útiles
// Object.getPrototypeOf() - obtener el prototipo
const proto = Object.getPrototypeOf(objeto);
// Object.setPrototypeOf() - establecer el prototipo (evitar en producción)
Object.setPrototypeOf(objeto, nuevoProto);
// Object.create() - crear con prototipo específico
const hijo = Object.create(padre);
// hasOwnProperty() - verificar propiedad propia
const persona = { nombre: "Alice" };
console.log(persona.hasOwnProperty("nombre")); // true
console.log(persona.hasOwnProperty("toString")); // false (heredado)
// isPrototypeOf() - verificar si está en la cadena
console.log(Object.prototype.isPrototypeOf(persona)); // true
// in operator - verifica propiedad propia o heredada
console.log("nombre" in persona); // true
console.log("toString" in persona); // true (heredado)
// Object.keys() - solo propiedades propias
console.log(Object.keys(persona)); // ["nombre"]
// for...in - itera sobre propiedades propias y heredadas
for (const clave in persona) {
if (persona.hasOwnProperty(clave)) {
console.log(clave, persona[clave]);
}
}
⚠️ Mejores Prácticas
- Preferir clases: Usa sintaxis de clase ES6 para código más limpio
- Evitar __proto__: Usa
Object.getPrototypeOf()en su lugar - No cambiar prototipos: Evita
Object.setPrototypeOf()por razones de rendimiento - Métodos en prototype: Coloca métodos compartidos en el prototipo, no en instancias
- Usar Object.create(null): Para objetos de diccionario/mapa
💡 Puntos Clave
- • JavaScript usa herencia prototípica, no clásica
- • Cada objeto tiene un prototipo interno ([[Prototype]])
- • Los métodos en el prototipo se comparten entre instancias
- • Las clases ES6 son azúcar sintáctico sobre prototipos
- • La cadena de prototipos permite la herencia de comportamiento
- • Usa
Object.create()para herencia explícita
Ejercicios de práctica
- • Implementa una jerarquía
Vehículo → Cocheusando funciones constructoras y prototipos. - • Compara el uso de memoria agregando un método a cada instancia vs agregarlo al prototipo.
- • Escribe una función que recorra la cadena de prototipos completa de un objeto.
- • Crea un objeto sin prototipo usando
Object.create(null)y verifica que no tengatoString.