Objetivos de aprendizaje
- Comprender la diferencia entre objeto literal, función constructora y class.
- Crear clases con constructor, propiedades y métodos.
- Implementar herencia con extends y super.
- Aplicar polimorfismo mediante override de métodos.
- Encapsular estado interno con campos privados (#).
POO en JavaScript (Parte I)
La Programación Orientada a Objetos (POO) es un paradigma que organiza el código en torno a objetos: entidades que combinan estado (datos) y comportamiento (métodos).
Evolución: de objeto literal a class
1) Objeto literal {}
La forma más simple. Sirve para una sola instancia:
const usuario = {
nombre: "Ana",
correo: "ana@email.com",
mostrarInfo() {
return `Usuario: ${this.nombre} — ${this.correo}`;
},
};
console.log(usuario.mostrarInfo()); // "Usuario: Ana — ana@email.com"Problema: si necesitas 100 usuarios, tienes que copiar ese objeto 100 veces.
2) Función constructora (ES5)
Una función que actúa como molde — se invoca con new:
function Usuario(nombre, correo) {
this.nombre = nombre;
this.correo = correo;
}
// Los métodos van en prototype para que los compartan todas las instancias
Usuario.prototype.mostrarInfo = function () {
return `Usuario: ${this.nombre} — ${this.correo}`;
};
const u1 = new Usuario("Ana", "ana@email.com");
const u2 = new Usuario("Luis", "luis@email.com");
console.log(u1.mostrarInfo()); // "Usuario: Ana — ana@email.com"
console.log(u2.mostrarInfo()); // "Usuario: Luis — luis@email.com"3) class (ES6+) — la forma moderna
La clase agrupa constructor + métodos en un bloque limpio y legible:
class Usuario {
constructor(nombre, correo) {
this.nombre = nombre;
this.correo = correo;
}
mostrarInfo() {
return `Usuario: ${this.nombre} — ${this.correo}`;
}
}
const u = new Usuario("Ana", "ana@email.com");
console.log(u.mostrarInfo()); // "Usuario: Ana — ana@email.com"
console.log(u instanceof Usuario); // trueRegla de oro: usa siempre
class. La función constructora es código legacy que encontrarás en proyectos antiguos.
Los 4 pilares de POO
Pilar 1: Encapsulamiento
Ocultar el estado interno del objeto y exponerlo solo a través de métodos controlados.
class CuentaBancaria {
#saldo; // campo privado — no accesible desde fuera
constructor(saldoInicial) {
this.#saldo = saldoInicial > 0 ? saldoInicial : 0;
}
depositar(monto) {
if (monto <= 0) return "Monto inválido";
this.#saldo += monto;
return `Depósito OK. Saldo: $${this.#saldo}`;
}
retirar(monto) {
if (monto <= 0) return "Monto inválido";
if (monto > this.#saldo) return "Fondos insuficientes";
this.#saldo -= monto;
return `Retiro OK. Saldo: $${this.#saldo}`;
}
get saldo() {
return this.#saldo; // getter: lectura controlada
}
}
const cuenta = new CuentaBancaria(10000);
console.log(cuenta.saldo); // 10000
console.log(cuenta.depositar(2500)); // "Depósito OK. Saldo: $12500"
console.log(cuenta.retirar(99999)); // "Fondos insuficientes"
// cuenta.#saldo → ❌ SyntaxError: private fieldPilar 2: Herencia
Una clase extiende otra, reutilizando su código y agregando o modificando comportamiento:
class Usuario {
constructor(nombre, correo) {
this.nombre = nombre;
this.correo = correo;
}
mostrarInfo() {
return `Usuario: ${this.nombre}`;
}
}
class Administrador extends Usuario {
constructor(nombre, correo, permiso) {
super(nombre, correo); // llama al constructor padre
this.permiso = permiso;
}
mostrarInfo() {
// override: reemplaza el método del padre
const estado = this.permiso ? "activo" : "inactivo";
return `Admin: ${this.nombre} (permiso ${estado})`;
}
}
const admin = new Administrador("Lucas", "lucas@email.com", true);
console.log(admin.mostrarInfo()); // "Admin: Lucas (permiso activo)"
console.log(admin instanceof Administrador); // true
console.log(admin instanceof Usuario); // true — hereda la cadenaPilar 3: Polimorfismo
El mismo método produce resultados distintos según el tipo de objeto:
class Vehiculo {
constructor(marca, modelo) {
this.marca = marca;
this.modelo = modelo;
}
describir() {
return `Vehículo: ${this.marca} ${this.modelo}`;
}
}
class Auto extends Vehiculo {
constructor(marca, modelo, puertas) {
super(marca, modelo);
this.puertas = puertas;
}
describir() {
return `Auto: ${this.marca} ${this.modelo} | ${this.puertas} puertas`;
}
}
class Moto extends Vehiculo {
constructor(marca, modelo, cilindrada) {
super(marca, modelo);
this.cilindrada = cilindrada;
}
describir() {
return `Moto: ${this.marca} ${this.modelo} | ${this.cilindrada}cc`;
}
}
const flota = [
new Auto("Toyota", "Corolla", 4),
new Moto("Yamaha", "R6", 600),
new Auto("Honda", "Civic", 4),
];
// Polimorfismo: la misma llamada `.describir()` produce salida distinta
flota.forEach(v => console.log(v.describir()));
// "Auto: Toyota Corolla | 4 puertas"
// "Moto: Yamaha R6 | 600cc"
// "Auto: Honda Civic | 4 puertas"Pilar 4: Abstracción
Exponer solo lo necesario y ocultar la complejidad interna. En JavaScript se logra con campos privados y métodos públicos bien diseñados:
class Notificador {
#destinatarios = [];
agregar(email) {
if (!email.includes("@")) throw new Error("Email inválido");
this.#destinatarios.push(email);
}
// El cómo se envía es un detalle oculto
#formatear(mensaje) {
return `[${new Date().toLocaleDateString("es-CL")}] ${mensaje}`;
}
enviar(mensaje) {
const texto = this.#formatear(mensaje);
// En producción: llamaría a un servicio de email
this.#destinatarios.forEach(d => console.log(`→ ${d}: ${texto}`));
}
}
const notif = new Notificador();
notif.agregar("ana@email.com");
notif.agregar("luis@email.com");
notif.enviar("Tu pedido fue despachado");
// → ana@email.com: [20/05/2026] Tu pedido fue despachado
// → luis@email.com: [20/05/2026] Tu pedido fue despachadoMétodos estáticos
Los métodos static pertenecen a la clase, no a las instancias:
class Matematica {
static sumar(a, b) { return a + b; }
static PI = 3.14159;
}
console.log(Matematica.sumar(3, 4)); // 7
console.log(Matematica.PI); // 3.14159
// new Matematica().sumar(1, 2) → TypeError: no es método de instancia