JavaScript

JavaScript

Tutorial JavaScript: Encapsulación

JavaScript encapsulación: técnicas y ejemplos. Aprende técnicas de encapsulación en JavaScript con ejemplos prácticos y detallados.

Aprende JavaScript y certifícate

Fundamentos de la encapsulación: Ocultamiento de datos y control de acceso en JavaScript

La encapsulación es uno de los pilares fundamentales de la programación orientada a objetos que permite ocultar los detalles internos de implementación de un objeto y exponer solo aquellas partes que son necesarias para su uso. En JavaScript, este concepto ha evolucionado significativamente con el tiempo, adaptándose a las particularidades del lenguaje.

Principios básicos de la encapsulación

La encapsulación se basa en dos conceptos principales:

  • Ocultamiento de datos: Restringir el acceso directo a ciertos componentes del objeto
  • Control de acceso: Proporcionar interfaces controladas para interactuar con esos datos

El objetivo principal es proteger la integridad de los datos internos de un objeto, evitando modificaciones accidentales o malintencionadas desde el exterior.

Convenciones de nomenclatura

Históricamente, JavaScript ha utilizado convenciones de nomenclatura para indicar que ciertas propiedades deberían tratarse como privadas:

class BankAccount {
  constructor(owner) {
    this.owner = owner;
    this._balance = 0; // El prefijo _ indica "privado por convención"
  }
  
  deposit(amount) {
    if (amount > 0) {
      this._balance += amount;
      return true;
    }
    return false;
  }
  
  getBalance() {
    return this._balance;
  }
}

En este ejemplo, _balance utiliza el prefijo _ para indicar que es una propiedad que no debería accederse directamente desde fuera de la clase. Sin embargo, esta es solo una convención y no impone ninguna restricción real:

const account = new BankAccount("Alice");
account.deposit(100);
console.log(account.getBalance()); // 100 (acceso correcto)
account._balance = 1000000; // Funciona, pero rompe la encapsulación

Closures para encapsulación

Antes de la introducción de clases en ES6, los closures eran el mecanismo principal para implementar encapsulación real en JavaScript:

function createCounter() {
  let count = 0; // Variable privada
  
  return {
    increment() {
      count++;
    },
    decrement() {
      count--;
    },
    getValue() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter.count); // undefined - no hay acceso directo

En este patrón, la variable count está encapsulada dentro del closure y solo es accesible a través de los métodos proporcionados. Este enfoque ofrece una verdadera encapsulación, ya que no hay forma de acceder directamente a la variable privada.

Getters y setters

Los getters y setters proporcionan una forma más elegante de controlar el acceso a las propiedades:

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }
  
  get celsius() {
    return this._celsius;
  }
  
  set celsius(value) {
    if (value < -273.15) {
      throw new Error("Temperature below absolute zero is not possible");
    }
    this._celsius = value;
  }
  
  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  }
  
  set fahrenheit(value) {
    this.celsius = (value - 32) * 5/9;
  }
}

Los getters y setters permiten:

  • Validar datos antes de asignarlos
  • Calcular valores derivados bajo demanda
  • Mantener la consistencia entre propiedades relacionadas
  • Ocultar la implementación interna
const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77

temp.celsius = 30;
console.log(temp.fahrenheit); // 86

// Validación en acción
try {
  temp.celsius = -300; // Error: Temperature below absolute zero
} catch (e) {
  console.log(e.message);
}

Métodos de objeto para control de acceso

JavaScript ofrece métodos nativos para controlar el comportamiento de las propiedades de un objeto:

class SafeBox {
  constructor(content) {
    this._content = content;
    
    // Hacer que _content no sea enumerable ni configurable
    Object.defineProperty(this, '_content', {
      enumerable: false,  // No aparece en Object.keys()
      configurable: false, // No puede ser eliminado
      writable: false     // No puede ser modificado directamente
    });
  }
  
  getContent(password) {
    if (password === "secret") {
      return this._content;
    }
    return "Access denied";
  }
}

Con Object.defineProperty() podemos:

  • Controlar si una propiedad es enumerable (visible en iteraciones)
  • Definir si es configurable (puede ser eliminada)
  • Establecer si es writable (puede ser modificada)
  • Implementar getters y setters personalizados

Símbolos para propiedades semi-privadas

Los símbolos introducidos en ES6 ofrecen otra forma de implementar propiedades semi-privadas:

const passwordSymbol = Symbol('password');

class User {
  constructor(username, password) {
    this.username = username;
    this[passwordSymbol] = password;
  }
  
  validatePassword(input) {
    return this[passwordSymbol] === input;
  }
}

const user = new User("admin", "1234");
console.log(user.validatePassword("1234")); // true
console.log(user[passwordSymbol]); // Accesible si se conoce el símbolo
console.log(Object.keys(user)); // ["username"] - el símbolo no aparece

Las propiedades basadas en símbolos:

  • No aparecen en iteraciones estándar como Object.keys()
  • No son accesibles directamente sin conocer el símbolo
  • Proporcionan un nivel de privacidad práctica aunque no absoluta

La encapsulación en JavaScript ha evolucionado desde simples convenciones hasta mecanismos más robustos. Aunque ninguna de estas técnicas tradicionales ofrece una encapsulación perfecta, proporcionan diferentes niveles de protección y control de acceso que son útiles en distintos contextos de desarrollo.

Técnicas modernas para la implementación de encapsulación: Campos privados y patrones de módulo

JavaScript ha evolucionado significativamente en los últimos años, introduciendo características nativas que permiten implementar una encapsulación más robusta y elegante. Las técnicas modernas ofrecen soluciones que van más allá de las convenciones y patrones tradicionales, proporcionando mecanismos de encapsulación con mayor integridad y claridad sintáctica.

Campos privados con # (ECMAScript 2022)

La adición más importante para la encapsulación en JavaScript moderno es la implementación de campos privados utilizando el prefijo #. Esta característica proporciona una verdadera privacidad a nivel de lenguaje:

class CreditCard {
  #number;
  #cvv;
  #expirationDate;
  name;

  constructor(number, cvv, expirationDate, name) {
    this.#number = number;
    this.#cvv = cvv;
    this.#expirationDate = expirationDate;
    this.name = name;
  }

  getLastFourDigits() {
    return this.#number.slice(-4);
  }

  isValid() {
    const now = new Date();
    return now < this.#expirationDate;
  }
}

A diferencia de las convenciones con guion bajo, los campos privados con # están estrictamente encapsulados:

const myCard = new CreditCard("1234567890123456", "123", new Date(2025, 0), "John Doe");

console.log(myCard.name); // "John Doe" - campo público
console.log(myCard.getLastFourDigits()); // "3456"

// Intentos de acceso directo generan errores
try {
  console.log(myCard.#number); // SyntaxError
} catch (e) {
  console.log("No se puede acceder a campos privados desde fuera");
}

Los campos privados con # ofrecen varias ventajas clave:

  • Son verdaderamente privados y no solo por convención
  • Generan un error de sintaxis si se intenta acceder desde fuera
  • No pueden ser descubiertos mediante técnicas de introspección como Object.keys()
  • Funcionan con métodos, getters y setters privados

Métodos privados y accesorios

La sintaxis de # también se aplica a métodos y accesorios privados:

class PaymentProcessor {
  #apiKey;
  #endpoint = "https://payment.example.com/api";

  constructor(apiKey) {
    this.#apiKey = apiKey;
  }

  async processPayment(amount, currency) {
    const data = await this.#sendRequest({
      amount,
      currency,
      timestamp: this.#generateTimestamp()
    });
    return this.#parseResponse(data);
  }

  #generateTimestamp() {
    return Date.now();
  }

  async #sendRequest(payload) {
    const response = await fetch(this.#endpoint, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${this.#apiKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(payload)
    });
    return response.json();
  }

  #parseResponse(data) {
    return {
      success: data.status === "approved",
      transactionId: data.id,
      timestamp: new Date(data.processed_at)
    };
  }
}

Este enfoque permite crear APIs limpias donde solo los métodos públicos son visibles para los consumidores de la clase, mientras que los detalles de implementación permanecen encapsulados.

Getters y setters privados

También podemos definir getters y setters privados para operaciones internas:

class TemperatureSensor {
  #celsius;
  #lastUpdated;
  #maxReadings = 100;
  #readings = [];

  constructor(initialCelsius) {
    this.#celsius = initialCelsius;
    this.#lastUpdated = new Date();
  }

  get temperature() {
    return {
      celsius: this.#celsius,
      fahrenheit: this.#fahrenheit,
      lastUpdated: this.#lastUpdated
    };
  }

  // Getter privado
  get #fahrenheit() {
    return this.#celsius * 9/5 + 32;
  }

  updateReading(newCelsius) {
    this.#celsius = newCelsius;
    this.#lastUpdated = new Date();
    this.#addReading(newCelsius);
    return this.temperature;
  }

  // Método privado
  #addReading(value) {
    this.#readings.push({
      value,
      timestamp: new Date()
    });
    
    if (this.#readings.length > this.#maxReadings) {
      this.#readings.shift();
    }
  }
}

Patrón de módulo moderno con ESM

El sistema de módulos nativo de JavaScript (ESM) proporciona otra capa de encapsulación a nivel de archivo:

// userService.js
const API_KEY = "secret_api_key"; // Variable privada al módulo

function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

export function createUser(userData) {
  if (!validateEmail(userData.email)) {
    throw new Error("Invalid email format");
  }
  
  // Lógica que usa API_KEY internamente
  return fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify(userData)
  }).then(res => res.json());
}

export function getUser(id) {
  // Implementación que usa API_KEY
}

// No exportamos validateEmail ni API_KEY

En otro archivo, solo podemos acceder a lo que se exporta explícitamente:

// app.js
import { createUser, getUser } from './userService.js';

// Podemos usar las funciones exportadas
createUser({ name: "Alice", email: "alice@example.com" });

// Pero no tenemos acceso a las partes privadas
// API_KEY y validateEmail no son accesibles aquí

Este patrón permite:

  • Encapsular variables y funciones a nivel de módulo
  • Exponer solo una API pública bien definida
  • Mantener la seguridad de datos sensibles como claves API
  • Organizar el código en unidades cohesivas

Combinando módulos y clases con campos privados

La combinación de módulos ESM y campos privados proporciona múltiples capas de encapsulación:

// database.js
const DB_CONNECTION_STRING = "mongodb://username:password@localhost:27017";

class DatabaseConnection {
  #connection = null;
  #retryCount = 0;
  #maxRetries = 3;

  async #connect() {
    try {
      // Lógica de conexión usando DB_CONNECTION_STRING
      this.#retryCount = 0;
      return true;
    } catch (error) {
      if (this.#retryCount < this.#maxRetries) {
        this.#retryCount++;
        return this.#connect();
      }
      throw new Error("Failed to connect to database");
    }
  }

  async query(sql, params) {
    if (!this.#connection) {
      await this.#connect();
    }
    // Implementación de la consulta
    return results;
  }
}

// Solo exportamos una instancia, no la clase ni la cadena de conexión
export const db = new DatabaseConnection();

Este enfoque proporciona:

  1. Encapsulación a nivel de módulo para la cadena de conexión
  2. Encapsulación a nivel de clase para los detalles de implementación
  3. Una API pública mínima (solo el método query)
  4. Patrón singleton para la conexión a la base de datos

WeakMaps para datos privados (enfoque alternativo)

Antes de los campos privados nativos, los WeakMaps ofrecían una solución elegante para almacenar datos privados:

// Enfoque alternativo para entornos que no soportan campos privados
const privateData = new WeakMap();

class User {
  constructor(name, role) {
    privateData.set(this, {
      name,
      role,
      loginAttempts: 0
    });
  }

  authenticate(password) {
    const data = privateData.get(this);
    
    if (password !== "correct-password") {
      data.loginAttempts++;
      if (data.loginAttempts >= 3) {
        return { success: false, locked: true };
      }
      return { success: false, attempts: data.loginAttempts };
    }
    
    data.loginAttempts = 0;
    return { 
      success: true, 
      userData: { name: data.name, role: data.role } 
    };
  }
}

Esta técnica sigue siendo útil en ciertos contextos, especialmente cuando se necesita compatibilidad con navegadores antiguos o cuando se trabaja con objetos que no son instancias de clases.

Las técnicas modernas de encapsulación en JavaScript proporcionan mecanismos robustos para proteger la integridad de los datos y crear APIs limpias y bien definidas. Los campos privados con # y el sistema de módulos ESM representan un avance significativo en la madurez del lenguaje para la programación orientada a objetos.

Encapsulación en arquitecturas complejas: Interfaces públicas, inmutabilidad y contratos de diseño

Cuando desarrollamos sistemas de software complejos, la encapsulación va más allá de simplemente ocultar datos. Se convierte en una herramienta arquitectónica fundamental que define cómo interactúan los diferentes componentes de un sistema. En arquitecturas modernas de JavaScript, la encapsulación se manifiesta a través de interfaces bien definidas, inmutabilidad y contratos de diseño explícitos.

Interfaces públicas estables

Una interfaz pública bien diseñada actúa como un contrato entre el componente y sus consumidores, permitiendo que la implementación interna evolucione sin afectar al código cliente:

// Módulo que expone una interfaz pública estable
export class UserRepository {
  // La interfaz pública se mantiene estable
  async findById(id) { /* implementación */ }
  async findByEmail(email) { /* implementación */ }
  async save(user) { /* implementación */ }
  async delete(id) { /* implementación */ }
  
  // Los métodos privados pueden cambiar libremente
  #validateUser(user) { /* implementación */ }
  #normalizeEmail(email) { /* implementación */ }
}

Las interfaces públicas efectivas siguen estos principios clave:

  • Minimalismo: Exponer solo lo estrictamente necesario
  • Consistencia: Mantener patrones coherentes en nombres y comportamientos
  • Completitud: Proporcionar todas las operaciones necesarias para usar el componente
  • Estabilidad: Cambiar con poca frecuencia para no romper el código cliente

Inmutabilidad como forma de encapsulación

La inmutabilidad es una técnica poderosa de encapsulación que garantiza que los objetos no cambien después de su creación, eliminando efectos secundarios inesperados:

class ImmutablePoint {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
    // Congelamos el objeto para prevenir la adición de propiedades
    Object.freeze(this);
  }

  get x() { return this.#x; }
  get y() { return this.#y; }

  // En lugar de modificar, creamos nuevas instancias
  translate(dx, dy) {
    return new ImmutablePoint(this.#x + dx, this.#y + dy);
  }

  scale(factor) {
    return new ImmutablePoint(this.#x * factor, this.#y * factor);
  }
}

Los objetos inmutables ofrecen varias ventajas arquitectónicas:

  • Seguridad en concurrencia: No hay riesgo de modificaciones simultáneas
  • Razonamiento simplificado: El estado no cambia inesperadamente
  • Facilidad para testing: Comportamiento predecible y reproducible
  • Compatibilidad con patrones funcionales: Facilita composición y transformaciones

Contratos de diseño explícitos

Los contratos de diseño formalizan las expectativas sobre cómo debe usarse un componente, reforzando la encapsulación mediante validaciones explícitas:

class BankAccount {
  #balance = 0;
  #minimumBalance;
  #owner;

  constructor(owner, initialDeposit = 0, minimumBalance = 0) {
    // Precondiciones - validamos antes de crear el objeto
    if (typeof owner !== 'object' || !owner.id || !owner.name) {
      throw new TypeError('Owner must be an object with id and name');
    }
    
    if (initialDeposit < minimumBalance) {
      throw new RangeError(`Initial deposit must be at least ${minimumBalance}`);
    }

    this.#owner = { ...owner }; // Copia defensiva
    this.#balance = initialDeposit;
    this.#minimumBalance = minimumBalance;
  }

  withdraw(amount) {
    // Precondiciones
    if (amount <= 0) {
      throw new RangeError('Withdrawal amount must be positive');
    }
    
    // Invariantes
    if (this.#balance - amount < this.#minimumBalance) {
      throw new Error('Insufficient funds');
    }
    
    this.#balance -= amount;
    
    // Postcondiciones
    return {
      success: true,
      newBalance: this.#balance,
      timestamp: new Date()
    };
  }
}

Los contratos de diseño incluyen:

  • Precondiciones: Requisitos que deben cumplirse antes de ejecutar una operación
  • Postcondiciones: Garantías sobre el estado después de la operación
  • Invariantes: Condiciones que siempre deben ser verdaderas durante la vida del objeto

Patrones de fachada para sistemas complejos

El patrón fachada proporciona una interfaz simplificada a un subsistema complejo, encapsulando la complejidad interna:

// Subsistemas complejos encapsulados
class PaymentProcessor { /* implementación compleja */ }
class InventoryManager { /* implementación compleja */ }
class ShippingService { /* implementación compleja */ }
class NotificationSystem { /* implementación compleja */ }

// Fachada que simplifica el uso del sistema
export class OrderService {
  #paymentProcessor = new PaymentProcessor();
  #inventory = new InventoryManager();
  #shipping = new ShippingService();
  #notifications = new NotificationSystem();

  async placeOrder(orderData) {
    try {
      // Validación de la orden
      this.#validateOrder(orderData);
      
      // Reserva de inventario
      await this.#inventory.reserveItems(orderData.items);
      
      // Procesamiento del pago
      const paymentResult = await this.#paymentProcessor.processPayment(
        orderData.payment
      );
      
      if (!paymentResult.success) {
        await this.#inventory.releaseItems(orderData.items);
        return { success: false, reason: 'payment_failed' };
      }
      
      // Creación del envío
      const shipment = await this.#shipping.createShipment(
        orderData.items,
        orderData.shippingAddress
      );
      
      // Notificación al cliente
      await this.#notifications.sendOrderConfirmation(
        orderData.customer,
        { orderId: shipment.trackingId }
      );
      
      return {
        success: true,
        orderId: shipment.trackingId,
        estimatedDelivery: shipment.estimatedDelivery
      };
    } catch (error) {
      // Manejo de errores y compensación
      return { success: false, reason: error.message };
    }
  }

  #validateOrder(orderData) {
    // Implementación de validación
  }
}

Este patrón:

  • Simplifica la API expuesta a los clientes
  • Oculta las dependencias y relaciones entre subsistemas
  • Centraliza la lógica de coordinación entre componentes
  • Reduce el acoplamiento entre el cliente y los subsistemas

Encapsulación a nivel de dominio con objetos de valor

Los objetos de valor encapsulan conceptos del dominio, garantizando su integridad y validez:

class EmailAddress {
  #value;
  
  constructor(email) {
    if (!this.#isValid(email)) {
      throw new Error('Invalid email format');
    }
    this.#value = email.toLowerCase();
  }
  
  #isValid(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
  
  get value() {
    return this.#value;
  }
  
  equals(other) {
    return other instanceof EmailAddress && other.value === this.#value;
  }
  
  toString() {
    return this.#value;
  }
}

class Money {
  #amount;
  #currency;
  
  constructor(amount, currency) {
    if (typeof amount !== 'number' || isNaN(amount)) {
      throw new TypeError('Amount must be a number');
    }
    
    if (typeof currency !== 'string' || currency.length !== 3) {
      throw new TypeError('Currency must be a 3-letter ISO code');
    }
    
    this.#amount = amount;
    this.#currency = currency.toUpperCase();
  }
  
  get amount() { return this.#amount; }
  get currency() { return this.#currency; }
  
  add(other) {
    if (!(other instanceof Money) || other.currency !== this.#currency) {
      throw new Error('Cannot add different currencies');
    }
    
    return new Money(this.#amount + other.amount, this.#currency);
  }
  
  multiply(factor) {
    return new Money(this.#amount * factor, this.#currency);
  }
  
  toString() {
    return `${this.#amount.toFixed(2)} ${this.#currency}`;
  }
}

Los objetos de valor:

  • Encapsulan reglas de validación específicas del dominio
  • Son inmutables por diseño
  • Tienen igualdad basada en valores (no en identidad)
  • Expresan intención más claramente que tipos primitivos

Composición sobre herencia para encapsulación flexible

La composición ofrece una forma más flexible de encapsulación que la herencia, permitiendo combinar comportamientos sin exponer la implementación interna:

// Comportamientos encapsulados como objetos independientes
class Logger {
  log(message) {
    console.log(`[LOG] ${message}`);
  }
}

class EventEmitter {
  #listeners = new Map();
  
  on(event, callback) {
    if (!this.#listeners.has(event)) {
      this.#listeners.set(event, []);
    }
    this.#listeners.get(event).push(callback);
  }
  
  emit(event, data) {
    const callbacks = this.#listeners.get(event) || [];
    callbacks.forEach(callback => callback(data));
  }
}

class HttpClient {
  async get(url) { /* implementación */ }
  async post(url, data) { /* implementación */ }
}

// Componente que utiliza composición para encapsular comportamientos
class UserService {
  #logger = new Logger();
  #events = new EventEmitter();
  #http = new HttpClient();
  
  async getUser(id) {
    this.#logger.log(`Fetching user ${id}`);
    
    try {
      const user = await this.#http.get(`/users/${id}`);
      this.#events.emit('user:loaded', user);
      return user;
    } catch (error) {
      this.#logger.log(`Error fetching user: ${error.message}`);
      this.#events.emit('user:error', { id, error });
      throw error;
    }
  }
  
  onUserLoaded(callback) {
    this.#events.on('user:loaded', callback);
  }
}

La composición:

  • Encapsula comportamientos en unidades independientes
  • Permite cambiar implementaciones sin afectar la interfaz
  • Facilita la prueba unitaria de cada componente
  • Evita los problemas de la herencia como el acoplamiento rígido

Encapsulación en arquitecturas de microservicios frontend

En arquitecturas frontend modernas, la encapsulación se extiende a componentes autónomos que encapsulan su estado, lógica y presentación:

// Componente encapsulado con estado interno y API pública
class ShoppingCart extends HTMLElement {
  #items = [];
  #root;
  
  constructor() {
    super();
    this.#root = this.attachShadow({ mode: 'closed' });
    this.#render();
  }
  
  // API pública
  addItem(product, quantity = 1) {
    const existingItem = this.#items.find(item => item.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      this.#items.push({ ...product, quantity });
    }
    
    this.#render();
    this.dispatchEvent(new CustomEvent('cart:changed', { 
      detail: { itemCount: this.itemCount }
    }));
  }
  
  removeItem(productId) {
    this.#items = this.#items.filter(item => item.id !== productId);
    this.#render();
    this.dispatchEvent(new CustomEvent('cart:changed', { 
      detail: { itemCount: this.itemCount }
    }));
  }
  
  get itemCount() {
    return this.#items.reduce((total, item) => total + item.quantity, 0);
  }
  
  get total() {
    return this.#items.reduce(
      (sum, item) => sum + (item.price * item.quantity), 
      0
    );
  }
  
  // Métodos privados
  #render() {
    this.#root.innerHTML = `
      <style>/* Estilos encapsulados */</style>
      <div class="cart">
        <h2>Your Cart (${this.itemCount} items)</h2>
        <ul>
          ${this.#renderItems()}
        </ul>
        <div class="total">Total: $${this.total.toFixed(2)}</div>
      </div>
    `;
    
    // Agregar event listeners
    this.#attachEventListeners();
  }
  
  #renderItems() {
    return this.#items.map(item => `
      <li data-id="${item.id}">
        ${item.name} - $${item.price} × ${item.quantity}
        <button class="remove">×</button>
      </li>
    `).join('');
  }
  
  #attachEventListeners() {
    const removeButtons = this.#root.querySelectorAll('.remove');
    removeButtons.forEach(button => {
      const li = button.closest('li');
      button.addEventListener('click', () => {
        this.removeItem(li.dataset.id);
      });
    });
  }
}

// Registrar el componente
customElements.define('shopping-cart', ShoppingCart);

Este enfoque de encapsulación:

  • Utiliza Shadow DOM para encapsular el DOM interno
  • Mantiene el estado privado inaccesible desde el exterior
  • Expone una API pública bien definida
  • Comunica cambios mediante eventos personalizados

La encapsulación en arquitecturas complejas de JavaScript va más allá del simple ocultamiento de datos, convirtiéndose en un principio arquitectónico que define cómo se estructuran, comunican y evolucionan los componentes de un sistema. Mediante interfaces públicas estables, inmutabilidad, contratos de diseño explícitos y patrones como fachada y composición, podemos construir sistemas robustos, mantenibles y adaptables a los cambios.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende JavaScript online

Ejercicios de esta lección Encapsulación

Evalúa tus conocimientos de esta lección Encapsulación con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

Todas las lecciones de JavaScript

Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a JavaScript y certifícate

Certificados de superación de JavaScript

Supera todos los ejercicios de programación del curso de JavaScript y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender el concepto de encapsulación en la programación orientada a objetos.
  2. Conocer las ventajas de aplicar la encapsulación en la organización y protección de la información interna de los objetos.
  3. Aprender cómo implementar la encapsulación en JavaScript utilizando diferentes enfoques como IIFE, clausuras, objetos literales y clases con getters y setters.
  4. Entender la diferencia entre propiedades y métodos públicos y privados, y cómo limitar el acceso a la información interna de los objetos.
  5. Conocer los pros y contras de los enfoques de encapsulación en JavaScript y saber cuándo utilizar cada uno según las necesidades del proyecto.
  6. Saber cómo la encapsulación mejora la modularidad y la mantenibilidad del código, y cómo evita la manipulación involuntaria de datos internos.