Propiedades y métodos de objetos
Introducción
En el artículo anterior exploramos las diferentes formas de crear objetos en JavaScript. Una vez creado un objeto, necesitamos entender cómo definir, modificar y manipular sus propiedades y métodos. Las propiedades son los datos asociados a un objeto, mientras que los métodos son las funciones que puede realizar.
La correcta definición y manipulación de propiedades y métodos es fundamental para diseñar objetos efectivos y aprovechar completamente el paradigma de programación orientada a objetos en JavaScript. En este artículo, veremos todas las opciones que nos ofrece JavaScript para trabajar con estos elementos, desde las formas más básicas hasta técnicas más avanzadas como los descriptores de propiedades.
Definición de propiedades
Las propiedades de un objeto son los datos que almacena. Podemos definirlas de varias maneras:
Al crear el objeto
La forma más común es definir propiedades al momento de crear el objeto:
const usuario = {
nombre: "Alberto",
edad: 28,
email: "alberto@ejemplo.com",
activo: true
};
Después de la creación
También podemos añadir propiedades a un objeto existente mediante asignación directa:
const producto = {};
// Añadir propiedades después de crear el objeto
producto.nombre = "Teclado mecánico";
producto.precio = 89.99;
producto.disponible = true;
producto.categoria = "Periféricos";
console.log(producto);
// Muestra: { nombre: 'Teclado mecánico', precio: 89.99, disponible: true, categoria: 'Periféricos' }
Esta flexibilidad es una de las características distintivas de JavaScript, ya que nos permite modificar dinámicamente los objetos incluso después de crearlos.
Usando notación de corchetes
Además de la notación de punto, podemos usar la notación de corchetes para definir propiedades, especialmente útil cuando el nombre de la propiedad es dinámico:
const config = {};
// Añadir propiedades con nombres dinámicos
const prefijo = "app";
config[prefijo + "_id"] = "12345";
config[prefijo + "_clave"] = "abc123";
console.log(config);
// Muestra: { app_id: '12345', app_clave: 'abc123' }
Métodos como funciones en objetos
Los métodos son simplemente funciones asignadas a propiedades de un objeto. Hay varias formas de definirlos:
Sintaxis tradicional
const calculadora = {
resultado: 0,
sumar: function(a, b) {
this.resultado = a + b;
return this.resultado;
},
restar: function(a, b) {
this.resultado = a - b;
return this.resultado;
}
};
console.log(calculadora.sumar(5, 3)); // Muestra: 8
console.log(calculadora.resultado); // Muestra: 8
Sintaxis abreviada (ES6)
JavaScript moderno nos permite una sintaxis más concisa para definir métodos:
const calculadora = {
resultado: 0,
sumar(a, b) {
this.resultado = a + b;
return this.resultado;
},
restar(a, b) {
this.resultado = a - b;
return this.resultado;
}
};
console.log(calculadora.restar(10, 4)); // Muestra: 6
Métodos con funciones flecha
Las funciones flecha tienen un comportamiento especial con respecto a this
, lo que puede ser útil o problemático dependiendo del caso:
const contador = {
valor: 0,
// Este método funciona correctamente con 'this'
incrementar() {
this.valor++;
return this.valor;
},
// Este método NO funcionará como se espera
incrementarArrow: () => {
// 'this' NO se refiere al objeto contador, sino al ámbito exterior
this.valor++;
return this.valor;
},
// Caso de uso válido para arrow functions en objetos
configurarTemporizador() {
this.valor = 0;
// Aquí la arrow function conserva el 'this' del método que la contiene
setTimeout(() => {
this.incrementar();
console.log(`Valor después de 1 segundo: ${this.valor}`);
}, 1000);
}
};
contador.incrementar(); // Funciona bien
// contador.incrementarArrow(); // No funcionará correctamente
contador.configurarTemporizador(); // Uso correcto de arrow function
Es importante entender que en la arrow function, this
no se vincula al objeto, sino que mantiene el valor del contexto que la rodea.
Añadir, modificar y eliminar propiedades
JavaScript ofrece gran flexibilidad para modificar objetos en tiempo de ejecución:
Añadir y modificar propiedades
const cliente = {
nombre: "Sofia",
edad: 32
};
// Añadir nueva propiedad
cliente.ciudad = "Barcelona";
// Modificar propiedad existente
cliente.edad = 33;
console.log(cliente);
// Muestra: { nombre: 'Sofia', edad: 33, ciudad: 'Barcelona' }
Eliminar propiedades
Para eliminar propiedades usamos el operador delete
:
const configuracion = {
usuario: "admin",
password: "1234",
temporal: true,
servidor: "principal"
};
// Eliminar la propiedad password por seguridad
delete configuracion.password;
// Eliminar con notación de corchetes
delete configuracion["temporal"];
console.log(configuracion);
// Muestra: { usuario: 'admin', servidor: 'principal' }
Descriptores de propiedades
JavaScript proporciona un sistema avanzado para controlar el comportamiento de las propiedades a través de descriptores. Cada propiedad tiene atributos que controlan si se puede cambiar, enumerar o reconfigurar.
Object.defineProperty()
Este método nos permite definir una propiedad con un control preciso sobre sus atributos:
const producto = {};
// Definir una propiedad con un descriptor
Object.defineProperty(producto, "nombre", {
value: "Monitor 27 pulgadas",
writable: true, // Si se puede modificar el valor
enumerable: true, // Si aparece en bucles for...in y Object.keys()
configurable: true // Si se puede eliminar o cambiar sus atributos
});
// Definir una propiedad que no se puede modificar
Object.defineProperty(producto, "id", {
value: "ABC123",
writable: false, // No se puede cambiar el valor
enumerable: true,
configurable: false // No se puede eliminar ni reconfigurar
});
producto.nombre = "Monitor 32 pulgadas"; // Esto funciona
// producto.id = "XYZ789"; // Esto NO funcionará (en modo estricto lanzará error)
console.log(producto);
Object.defineProperties()
Podemos definir múltiples propiedades a la vez:
const empleado = {};
Object.defineProperties(empleado, {
nombre: {
value: "Carmen",
writable: true,
enumerable: true,
configurable: true
},
id: {
value: "EMP001",
writable: false,
enumerable: true,
configurable: false
},
departamento: {
value: "Desarrollo",
writable: true,
enumerable: true,
configurable: true
}
});
console.log(empleado);
// Muestra: { nombre: 'Carmen', id: 'EMP001', departamento: 'Desarrollo' }
Obtener descriptores de propiedades
Podemos inspeccionar los descriptores de una propiedad:
const usuario = {
nombre: "Diego"
};
// Añadir una propiedad con descriptor personalizado
Object.defineProperty(usuario, "id", {
value: 12345,
writable: false,
enumerable: false
});
// Obtener el descriptor de una propiedad
const descripcionNombre = Object.getOwnPropertyDescriptor(usuario, "nombre");
const descripcionId = Object.getOwnPropertyDescriptor(usuario, "id");
console.log(descripcionNombre);
/* Muestra:
{
value: 'Diego',
writable: true,
enumerable: true,
configurable: true
}
*/
console.log(descripcionId);
/* Muestra:
{
value: 12345,
writable: false,
enumerable: false,
configurable: true
}
*/
Propiedades y métodos calculados
ES6 introdujo la capacidad de crear propiedades y métodos con nombres calculados dinámicamente:
const prefijo = "app";
const accion = "calcular";
const app = {
// Propiedad calculada
[`${prefijo}_version`]: "1.0.0",
// Método calculado
[`${accion}Total`](a, b, c) {
return a + b + c;
}
};
console.log(app.app_version); // Muestra: 1.0.0
console.log(app.calcularTotal(10, 20, 30)); // Muestra: 60
Esta característica es muy útil cuando necesitamos crear propiedades o métodos de forma dinámica.
Accesores (getters y setters)
Los accesores nos permiten ejecutar código cuando se accede o se modifica una propiedad. Esto es útil para validaciones, transformaciones o cualquier lógica que deba ejecutarse automáticamente:
Definición con sintaxis literal
const cuenta = {
// Propiedad privada (convención)
_saldo: 0,
// Getter: se ejecuta cuando se accede a cuenta.saldo
get saldo() {
console.log("Leyendo el saldo...");
return this._saldo;
},
// Setter: se ejecuta cuando se asigna un valor a cuenta.saldo
set saldo(nuevoSaldo) {
if (nuevoSaldo < 0) {
console.error("No se permite saldo negativo");
return;
}
console.log(`Cambiando saldo a ${nuevoSaldo}`);
this._saldo = nuevoSaldo;
},
// Métodos normales
depositar(cantidad) {
this.saldo += cantidad;
},
retirar(cantidad) {
this.saldo -= cantidad;
}
};
// Usar los accesores (parecen propiedades normales en el uso)
cuenta.saldo = 1000;
console.log(cuenta.saldo); // Activa el getter
cuenta.depositar(500);
console.log(cuenta.saldo); // 1500
cuenta.saldo = -200; // No se asignará por la validación en el setter
Usando Object.defineProperty()
También podemos definir getters y setters usando descriptores de propiedades:
const producto = {
_precio: 0,
_descuento: 0
};
Object.defineProperty(producto, "precio", {
get: function() {
return this._precio;
},
set: function(valor) {
if (valor < 0) throw new Error("El precio no puede ser negativo");
this._precio = valor;
},
enumerable: true,
configurable: true
});
Object.defineProperty(producto, "precioFinal", {
get: function() {
return this._precio * (1 - this._descuento / 100);
},
enumerable: true,
configurable: true
});
Object.defineProperty(producto, "descuento", {
get: function() {
return this._descuento;
},
set: function(valor) {
if (valor < 0 || valor > 100) {
throw new Error("El descuento debe estar entre 0 y 100");
}
this._descuento = valor;
},
enumerable: true,
configurable: true
});
// Usar las propiedades con accesores
producto.precio = 100;
producto.descuento = 15;
console.log(producto.precio); // 100
console.log(producto.descuento); // 15
console.log(producto.precioFinal); // 85
Buenas prácticas en el diseño de objetos
A continuación, presentamos algunas recomendaciones para crear objetos efectivos y mantenibles:
1. Usar nombres descriptivos
Elige nombres de propiedades y métodos que sean autoexplicativos:
// Poco claro
const u = {
n: "Juan",
e: "juan@ejemplo.com",
p: "123456"
};
// Mejor
const usuario = {
nombre: "Juan",
email: "juan@ejemplo.com",
password: "123456"
};
2. Agrupar funcionalidades relacionadas
Organiza tu objeto de manera que las propiedades y métodos relacionados estén juntos:
const carrito = {
// Datos
items: [],
total: 0,
// Métodos de manipulación de items
agregarItem(producto, cantidad = 1) {
// Implementación
},
eliminarItem(productoId) {
// Implementación
},
// Métodos de cálculo
calcularTotal() {
// Implementación
},
calcularImpuestos() {
// Implementación
},
// Métodos de checkout
procesarPago() {
// Implementación
},
generarFactura() {
// Implementación
}
};
3. Usar "_" para propiedades "privadas"
Aunque JavaScript no tiene verdaderas propiedades privadas hasta las versiones más recientes, es común usar el prefijo guion bajo para indicar que una propiedad no debería accederse directamente:
const usuario = {
_password: "12345", // Indicación de que es "privada"
// Proporcionar métodos controlados para acceder/modificar
cambiarPassword(passwordAntiguo, passwordNuevo) {
if (passwordAntiguo === this._password) {
this._password = passwordNuevo;
return true;
}
return false;
},
verificarPassword(password) {
return password === this._password;
}
};
4. Usar métodos para operaciones complejas
Encapsula la lógica compleja en métodos en lugar de exponerla directamente:
const reproductor = {
canciones: ["Canción 1", "Canción 2", "Canción 3"],
indiceActual: 0,
reproduciendo: false,
// Métodos para encapsular la lógica
reproducir() {
this.reproduciendo = true;
return `Reproduciendo: ${this.cancionActual()}`;
},
pausar() {
this.reproduciendo = false;
return "Pausado";
},
siguiente() {
this.indiceActual = (this.indiceActual + 1) % this.canciones.length;
return this.reproduciendo ? this.reproducir() : `Seleccionado: ${this.cancionActual()}`;
},
anterior() {
this.indiceActual = (this.indiceActual - 1 + this.canciones.length) % this.canciones.length;
return this.reproduciendo ? this.reproducir() : `Seleccionado: ${this.cancionActual()}`;
},
cancionActual() {
return this.canciones[this.indiceActual];
}
};
console.log(reproductor.reproducir()); // Reproduciendo: Canción 1
console.log(reproductor.siguiente()); // Reproduciendo: Canción 2
console.log(reproductor.pausar()); // Pausado
console.log(reproductor.siguiente()); // Seleccionado: Canción 3
5. Evitar objetos demasiado grandes
Si un objeto tiene demasiadas propiedades y métodos, considera dividirlo en objetos más pequeños y especializados:
// En lugar de un objeto gigante con todo
const gestorUsuarios = {
/* muchas propiedades y métodos */
};
// Dividir en módulos más pequeños
const autenticacion = {
/* métodos de autenticación */
};
const perfilUsuario = {
/* gestión de perfil */
};
const permisosUsuario = {
/* gestión de permisos */
};
Resumen
En este artículo hemos explorado las diferentes formas de definir y manipular propiedades y métodos en objetos JavaScript. Hemos visto desde las técnicas básicas como la asignación directa, hasta conceptos avanzados como los descriptores de propiedades y los accesores (getters y setters).
Las propiedades y métodos son el corazón de la programación orientada a objetos en JavaScript, y dominar su uso nos permite crear estructuras de datos efectivas y código más mantenible. La flexibilidad de JavaScript al permitirnos añadir, modificar y eliminar propiedades dinámicamente es una de sus características más potentes.
En el próximo artículo, profundizaremos en cómo acceder a las propiedades de los objetos de manera eficiente, explorando diferentes técnicas y patrones.