Fundamentos de Objetos, Clases y Programación Orientada a Objetos en JavaScript

En ECMAScript, un objeto es una colección desordenada de propiedades, donde cada una mapea un nombre a un valor, que puede ser dato o función. Este concepto se asemeja a una tabla hash.

// Creación con constructor
const coche = new Object();
coche.marca = "Toyota";
coche.arrancar = function() { console.log("Arrancando..."); }

// Creación con notación literal
const vehiculo = {
    modelo: "Corolla",
    acelerar() { console.log("Acelerando..."); }
}

Tipos de Propiedades

Las propiedades de datos almacenan un valor y se describen con cuatro características internas:

  • [[Configurable]]: Define si puede ser eliminada o reconfigurada.
  • [[Enumerable]]: Define si aparece en ciclos for-in.
  • [[Writable]]: Define si su valor puede ser modificado.
  • [[Value]]: Almacena el valor real de la propiedad.

Se pueden modificar estas características con Object.defineProperty():

const cuenta = {};
Object.defineProperty(cuenta, "saldo", {
    writable: false,
    valor: 1000,
    configurable: false
});
// cuenta.saldo = 2000; // Ignorado en modo no estricto, error en estricto.

Las propiedades de acceso (getter/setter) no almacenan datos directamente, sino que definen funciones de lectura/escritura:

const termometro = {
    _celsius: 25,
    get fahrenheit() {
        return this._celsius * 9/5 + 32;
    },
    set fahrenheit(valor) {
        this._celsius = (valor - 32) * 5/9;
    }
};
console.log(termometro.fahrenheit); // 77
termometro.fahrenheit = 86;
console.log(termometro._celsius);   // 30

Definición Múltiple y Descriptores

Object.defineProperties() permite definir múltiples propiedades a la vez. Para leer los descriptores de una propiedad se usa Object.getOwnPropertyDescriptor().

Combinación e Igualdad de Objetos

Object.assign() copia las propiedades enumerables y propias de uno o más objetos fuente a un objeto destino (shallow copy).

const destino = {};
const fuente1 = { id: 1 };
const fuente2 = { nombre: "Test" };
const resultado = Object.assign(destino, fuente1, fuente2);
// resultado y destino ahora son { id: 1, nombre: "Test" }

La igualdad estricta (===) tiene peculiaridades con +0/-0 y NaN. Object.is() resuelve estos casos.

Creación de Objetos

Se puedan usar varios patrones para crear objetos con estructuras compartidas.

Patrón Fábrica

Utiliza una función para crear y devolver un objeto, sin definir un tipo específico.

function crearEmpleado(nombre, puesto) {
    const instancia = {};
    instancia.nombre = nombre;
    instancia.puesto = puesto;
    instancia.presentarse = function() {
        console.log(`Soy ${this.nombre}, ${this.puesto}.`);
    };
    return instancia;
}

Patrón Constructor

Utiliza new con una función constructora para crear instancias de un tipo específico.

function Empleado(nombre, puesto) {
    this.nombre = nombre;
    this.puesto = puesto;
    this.presentarse = function() {
        console.log(`Soy ${this.nombre}, ${this.puesto}.`);
    };
}
const ana = new Empleado("Ana", "Ingeniera");
const carlos = new Empleado("Carlos", "Diseñador");
// ana.presentarse() y carlos.presentarse() son funciones distintas.

Patrón Prototipo

Las propiedades y métodos se definen en el prototipo del constructor, compartidos por todas las instancias.

function Dispositivo(tipo) {
    this.tipo = tipo;
}
Dispositivo.prototype.encender = function() {
    console.log(`${this.tipo} encendido.`);
};

const movil = new Dispositivo("Móvil");
const portatil = new Dispositivo("Portátil");
// movil.encender y portatil.encender apuntan a la misma función.

La cadena de prototipos se puede explorar con __proto__ o Object.getPrototypeOf().

Herencia

JavaScript implementa la herencia mediante la cadena de prototipos.

Cadena de Prototipos

El prototipo de un constructor puede ser una instancia de otro, formando una cadena.

function Forma(color) {
    this.color = color;
}
Forma.prototype.dibujar = function() { /* ... */ };

function Circulo(radio, color) {
    Forma.call(this, color); // "Robo" del constructor para heredar propiedades de instancia
    this.radio = radio;
}
// Hereda métodos del prototipo de Forma
Circulo.prototype = Object.create(Forma.prototype);
Circulo.prototype.constructor = Circulo;
Circulo.prototype.calcularArea = function() {
    return Math.PI * this.radio ** 2;
};

Herencia Combinada

Combina la herencia de prototipos (para métodos) y el robo de constructor (para propiedades de instancia).

function Base(config) {
    this.config = config;
}
Base.prototype.obtenerConfig = function() { return this.config; };

function Implementacion(valor, config) {
    Base.call(this, config); // Hereda propiedades de instancia
    this.valor = valor;
}
Implementacion.prototype = Object.create(Base.prototype);
Implementacion.prototype.constructor = Implementacion;
// Añade métodos específicos al prototipo de Implementacion

Clases en ES6

La sintaxis class es azúcar sintáctico sobre el sistema basado en prototipos.

Definición y Constructor

class Animal {
    constructor(especie) {
        this.especie = especie;
    }
    respirar() {
        console.log("Inhala... Exhala...");
    }
    // Método estático (perteneciente a la clase, no a instancias)
    static crearConEspecieDesconocida() {
        return new Animal("Desconocida");
    }
}
const perro = new Animal("Canino");
perro.respirar();

Herencia con Clases

class Mascota extends Animal {
    constructor(especie, nombre) {
        super(especie); // Llama al constructor de la clase base
        this.nombre = nombre;
    }
    // Sobrescritura de método
    respirar() {
        console.log(`${this.nombre} respira.`);
    }
}
const miGato = new Mascota("Felino", "Misu");
miGato.respirar(); // "Misu respira."

Miembros de Clace

  • Miembros de Instancia: Definidos en el constructor con this.
  • Métodos de Prototipo: Definidos directamente en el cuerpo de la clase.
  • Métodos Estáticos: Prefijados con static, pertenecen a la clase.
  • Campos Públicos/Privados (proposal): Sintaxis moderna para declarar propiedades fuera del constructor.
class Configurador {
    #clavePrivada = "abc123"; // Campo privado (proposal)
    version = "1.0";         // Campo público

    getClave() {
        return this.#clavePrivada;
    }
    static desdeJSON(json) {
        // Método estático
        const obj = JSON.parse(json);
        const conf = new Configurador();
        conf.version = obj.version;
        return conf;
    }
}

Mixins

Para componer comportamientos de múltiples fuentes, se pueden usar mixins basados en funciones que devuelven clases extendidas.

const Serializable = (SuperClase) => class extends SuperClase {
    serializar() {
        return JSON.stringify(this);
    }
};

const Auditable = (SuperClase) => class extends SuperClase {
    registrarCambio() {
        console.log("Cambio registrado.");
    }
};

class Documento extends Serializable(Auditable(Object)) {
    constructor(titulo) {
        super();
        this.titulo = titulo;
    }
}
const doc = new Documento("Contrato");
doc.registrarCambio();
console.log(doc.serializar());

Etiquetas: JavaScript Programación Orientada a Objetos Prototipos herencia Clases ES6

Publicado el 6-12 03:13