Encapsulamiento Moderno: Campos Privados (#), Getters y Setters
Aprende a proteger el estado interno de tus clases mediante la sintaxis nativa de campos privados (#) de ES2022 y controla el acceso con getters y setters.
La Larga Búsqueda de la Privacidad en JavaScript
Durante más de 20 años, JavaScript no tuvo ningún mecanismo oficial para declarar propiedades privadas como private en PHP o Java.
Los programadores recurrían a convenciones como anteponer un guión bajo (this._saldo = 100;), pidiéndole amablemente a otros desarrolladores que "por favor no lo modificaran". Evidentemente, esto era un falso encapsulamiento: cualquiera podía alterar el valor por error y romper la aplicación.
La Solución Definitiva: Campos Privados con `#` (ES2022)
A partir de la especificación ECMAScript 2022, JavaScript incorporó campos y métodos verdaderamente privados utilizando el prefijo de almohadilla #.
# es completamente inaccesible desde el exterior. Si intentas escribir objeto.#saldo fuera de la clase, el motor de JavaScript lanzará un SyntaxError fatal impidiendo cualquier intento de lectura o modificación.
Diagrama: La Frontera de Seguridad en Clases JS
Figura 5.1: Separación estricta entre la zona pública accesible y la zona interna con hash (#).
Getters y Setters Modernos
Para exponer de forma controlada o formateada el valor de un campo privado, utilizamos las palabras clave get y set:
class Usuario {
#nombre;
get nombre() {
return this.#nombre.toUpperCase(); // Acceso tipo propiedad: u.nombre
}
set nombre(valor) {
if (!valor || valor.trim().length === 0) throw new Error("Nombre inválido");
this.#nombre = valor.trim();
}
}
⚡ Billetera Digital con Campos Privados (#), PIN y Validaciones
Ejecuta el código en el Sandbox y observa cómo los campos con # bloquean accesos indebidos:
class BilleteraDigital {
// 1. Declaración obligatoria de Campos Privados (#)
#saldo = 0;
#pin;
#historialTransacciones = [];
constructor(titular, saldoInicial, pin) {
this.titular = titular; // Propiedad pública
this.#saldo = Math.max(0, saldoInicial);
this.#pin = String(pin);
this.#registrarEvento(`Cuenta creada con saldo inicial de $${this.#saldo}`);
}
// 2. Método Privado (Solo utilizable dentro de la clase)
#registrarEvento(descripcion) {
this.#historialTransacciones.push({
fecha: new Date().toLocaleTimeString(),
detalle: descripcion
});
}
#verificarPin(pinIngresado) {
return String(pinIngresado) === this.#pin;
}
// 3. Getter Público: Permite leer el saldo sin permitir alterarlo directamente
get saldo() {
return `$${this.#saldo.toFixed(2)}`;
}
// 4. Métodos Públicos Seguros
depositar(monto) {
if (monto <= 0) {
console.warn("⚠️ El monto a depositar debe ser mayor a 0.");
return;
}
this.#saldo += monto;
this.#registrarEvento(`Depósito de +$${monto.toFixed(2)}`);
console.log(` Depósito exitoso. Nuevo saldo disponible: ${this.saldo}`);
}
transferir(monto, cuentaDestino, pinIngresado) {
if (!this.#verificarPin(pinIngresado)) {
console.error("⛔ PIN de seguridad incorrecto. Operación bloqueada.");
return false;
}
if (monto > this.#saldo) {
console.error(`❌ Fondos insuficientes. Intentas transferir $${monto} pero tienes ${this.saldo}`);
return false;
}
this.#saldo -= monto;
this.#registrarEvento(`Transferencia a [${cuentaDestino}] de -$${monto.toFixed(2)}`);
console.log(` Transferencia de $${monto.toFixed(2)} a ${cuentaDestino} completada con éxito.`);
return true;
}
verHistorial(pinIngresado) {
if (!this.#verificarPin(pinIngresado)) {
console.error("⛔ Acceso denegado al historial.");
return;
}
console.log(`\n=== HISTORIAL DE ${this.titular.toUpperCase()} ===`);
this.#historialTransacciones.forEach(t => console.log(`• [${t.fecha}] ${t.detalle}`));
}
}
// ==========================================
// PRUEBA EN EL SANDBOX
// ==========================================
const miBilletera = new BilleteraDigital("Valeria Torres", 500, "1234");
console.log(`Titular público: ${miBilletera.titular}`);
console.log(`Saldo inicial (vía getter): ${miBilletera.saldo}`);
console.log("\n--- OPERACIONES CONTROLADAS ---");
miBilletera.depositar(200);
miBilletera.transferir(150, "Carlos Mendoza", "9999"); // Error de PIN
miBilletera.transferir(150, "Carlos Mendoza", "1234"); // PIN Correcto
miBilletera.verHistorial("1234");
// DEMOSTRACIÓN DE BLINDAJE PRIVADO:
// Intento de hackear el saldo desde fuera:
// miBilletera.#saldo = 999999; // ❌ Descomentar esto causa: SyntaxError: Private field '#saldo' must be declared in an enclosing class
Mini-Quiz de Retención
Comprueba tu comprensión antes de pasar a la siguiente lección¿Cuál es el símbolo oficial introducido en el estándar de JavaScript moderno (ES2022) para declarar un campo o método como estrictamente privado dentro de una clase?
¿Tienes dudas con este tema?
Sócrates AI puede darte analogías y explicarte este código paso a paso.🚀 ¿Quieres dominar Manual de Programación Orientada a Objetos en JavaScript (POO) y crear proyectos reales?
Aprende arquitectura limpia, desarrollo web moderno, buenas prácticas y despliegue en producción con nuestro Bootcamp Fullstack Developer con mentorías en vivo.
🏛️ Autoría y Propiedad Intelectual: Obra educativa original de David Amador para Kilopolis Academia (Solucionáticos), estructurada sobre las especificaciones y estándares abiertos del W3C, el estándar vivo de WHATWG y la documentación oficial de código abierto. © Todos los derechos reservados.