T5.6 Objetos
Objetos¶
En JS los objetos se crean a partir de prototipos... aunque desde ECMAScript 5 también podemos crearlos a partir de clases.
En realidad tenemos 6 métodos para crear objetos. Comencemos por los prototipos:
1. Declaración literal¶
A partir de una declaración literal del objeto:
const personaLiteral = {
nombre: "Juan",
apellidos: ["Fernández", "González"],
anhoNacimiento: 1950,
getEdad: function (){ return 2025-this.anhoNacimiento; }
};
console.log(personaLiteral);
// Acceso por propiedad o método (con el punto)
console.log(personaLiteral.nombre + " tiene " + personaLiteral.getEdad() + " años.");
// Acceso a propiedad como un "array"
console.log(`Primer apellido ${personaLiteral['apellidos'][0]}`);
personaLiteral.apellidos[2] = "Juanjo";
console.log("Nombre: ", personaLiteral['apellidos'] );
2. A partir de un prototipo¶
Añadiendo propiedes y métodos a un objeto vacío, u otro objeto:
const cocheVacio = {};
console.log(cocheVacio);
cocheVacio.nombre = "Opel";
console.log(cocheVacio);
cocheVacio.acelerar = function (velocidad) { this.velocidad += velocidad; };
cocheVacio.getVelocidad = function () { return this.velocidad; };
console.log(cocheVacio);
try {
console.log(cocheVacio.getVelocidad());
} catch (e) {
console.error(e);
}
// Añadimos más métodos:
cocheVacio.detener = function () { this.velocidad = 0; };
console.log(cocheVacio);
cocheVacio.acelerar(100);
console.log(`Después de acelerar 100 = ${cocheVacio.getVelocidad()}`);
cocheVacio.detener();
console.log(`Después de detener el vehículo = ${cocheVacio.getVelocidad()}`);
cocheVacio.acelerar(50);
console.log(`Después de volver a acelerar = ${cocheVacio.getVelocidad()}`);
// NO existe privacidad en este objeto:
console.log(cocheVacio.velocidad);
// O añadiendo a persona
personaLiteral.altura = 175;
console.log(personaLiteral);
Importante: cuidado que los objetos se asignan con referencias (como en Java), por lo que podemos meter la pata con facilidad
// 1. Creamos un objeto
const usuario = {
nombre: "John Doe",
edad: 14,
verificado: false
};
// 2. Lo "copiamos"
const nuevoUsuario = usuario;
console.log(nuevoUsuario); // {nombre: 'John Doe', edad: 14, verificado: false}
// 3. Modificamos el nuevo
nuevoUsuario.nombre = "Jane Doe";
console.log(nuevoUsuario); // {nombre: 'Jane Doe', edad: 14, verificado: false}
// 4. Verificamos el original
console.log(usuario); // {nombre: 'Jane Doe', edad: 14, verificado: false}
Veamos entonces como clonarlo correctamente:
// Creando un objeto a partir de otro
let clon = Object.create(usuario);
clon.nombre = "cero"
console.log(clon, usuario);
// Usando el Operador de Propagación (Spread Operator)
let clon1 = { ...usuario };
clon1.nombre = "uno";
console.log(clon1, usuario);
// Usando Object.assign()
let clon2 = Object.assign({}, usuario);
clon2.nombre = "dos";
console.log(clon2, usuario);
// Usando JSON.parse()
let clon3 = JSON.parse(JSON.stringify(usuario))
clon3.nombre = "tres";
console.log(clon3, usuario);
También podemos añadir funciones:
clon1.saluda = () => console.log('Hola mundo');
clon1.saluda();
Nota: Ésto añade la declaración de la función al objeto.
3. new Object()¶
Con el constructor new Object():
const otroVacio = new Object();
otroVacio.nombre = "Vacio";
console.log(otroVacio);
Nota: es raro. No se adapta al formato clásico de JS ni al de otros lenguajes como Java por lo que es raro verlo (nadie está cómodo con este formato). Mejor no usar.
4. Función constructora¶
function Persona(nombre, edad, idiomas) {
this.nombre = nombre; // Asignar propiedades
this.edad = edad;
this.idiomas = idiomas;
// Método
/* Esto genera una función en cada objeto (lo del punto 2)
this.saludar = function() {
console.log(`Hola, soy ${this.nombre} y tengo ${this.edad} años.`);
};*/
}
// Ésto genera una función en el prototipo de la clase
Persona.prototype.saludar = function() {
console.log(`Hola, soy ${this.nombre} y tengo ${this.edad} años.`);
};
// Crear instancias del objeto
const manuel = new Persona('Manuel', 36, ['Español', 'Inglés', 'Italiano']);
console.log(manuel);
manuel.saludar();
Vemos que este método ya es algo similar a lo que conocemos en Java, sólo tenemos la diferencia de las funciones que podemos asociarlas a los objetos o al prototipo (igual para todos los objetos).
5. Con class¶
Esto si que es lo que ya conocemos de Java:
class OtraPersona {
// Constructor para inicializar propiedades
constructor(nombre, edad, idiomas) {
this.nombre = nombre;
this.edad = edad;
this.idiomas = idiomas;
}
// Método para saludar
saludar() {
console.log(`Hola, soy ${this.nombre} y tengo ${this.edad} años.`);
}
// Método para listar idiomas (con lambdas)
listarIdiomas = () => console.log(`Hablo: ${this.idiomas.join(', ')}`);
}
// Crear instancias de la clase
const pedro = new OtraPersona('Pedro', 36, ['Español', 'Inglés', 'Italiano']);
console.log(pedro);
pedro.saludar();
pedro.listarIdiomas()
6. Con Object.create(prototipo)¶
Esta última opción es muy nativa de JS:
const bici = {
velocidad: 0,
acelerar: function (velocidad) { this.velocidad += velocidad; },
getVelocidad: function () { return this.velocidad; },
toString: function(){ return this.getVelocidad(); }
}
const miBici = Object.create(bici);
miBici.acelerar(10);
console.log("miBici",miBici);
miBici.acelerar(30);
console.log(miBici.getVelocidad());
console.log(miBici + ' km/h');
El método toString()¶
Repitamos la creación de objetos añadiendo el método toString() para mejorar la interacción con el mundo:
const bici = {
velocidad: 0,
acelerar: function (velocidad) { this.velocidad += velocidad; },
getVelocidad: function () { return this.velocidad; },
toString: function(){ return this.getVelocidad(); }
}
const miBici = Object.create(bici);
miBici.acelerar(10);
console.log(miBici, miBici.toString());
console.log(miBici + ' km/h');
typeof vs instanceof¶
console.log( typeof pedro, typeof miBici)
if ( pedro instanceof Persona ) console.log(`${pedro} -> Es un objeto de tipo "Persona"`);
if ( pedro instanceof OtraPersona ) console.log(pedro, ` -> Es un objeto de tipo "${pedro.constructor.name}"`);
Desectructuración de objetos:¶
let a, b, c;
({a, b, c} = { a: 3, b: 4, c: 5 });
let objeto = {a, b, c};
// Composición
let objetoCompuesto = {uno: 1, dos: 2, ...objeto};
console.log("Objeto de trabajo = ", objetoCompuesto);
console.log("Obtenemos el 2º atributo", objetoCompuesto.b);
Ejercicios¶
- Crea un objeto persona y a partir de él otro con una propiedad y método más.
- Crea una clase persona e instanciala.
- Crea una clase alumno que deriva de persona e instanciala.
Modificadores de privacidad¶
De forma clásica Javascript NO tenía modificadores de privacidad, aunque existe la convención de que un atributo que comienza por guión bajo (_) es un atributo “protegido” (no es verdad, pero todos actuamos como que sí).
let persona = {
_edad = 23;
}
// Podríamos (aunque NO deberíamos):
console.log(persona._edad);
PRIVACIDAD real en JS¶
En JS moderno SÍ existe el modificador de privacidad (#) que impide el acceso a las propiedades de forma directa.
class Persona {
#edad;
constructor(nombre, edad) {
this._nombre = nombre;
this.#edad = edad;
}
toString(){ return `Persona de ${this.#edad} años.`}
}
let persona = new Persona('Luis', 23);
console.log(persona._nombre); // No deberíamos pero -> 'Luis'
console.log(persona.#edad); // Uncaught SyntaxError: reference to undeclared private field or method #edad debugger eval code:1:21
console.log( persona.toString() );
La edad está, pero no podemos acceder de forma directa. Sólo podemos a través de métodos.
Atributos y métodos de clase¶
AMPLIACIÓN: ... eso otro día.