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 ciclosfor-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());