Módulo 5: Objetos y Arrays Avanzados
⏱️ Tiempo estimado: 8-9 horas
📋 Prerequisites
- Completar Nivel Principiante
- Entender funciones y scope
- Conocer métodos básicos de arrays
- Familiaridad con objetos literales
🎯 Objetivos del Módulo
- Dominar manipulación avanzada de arrays
- Implementar patrones de diseño con objetos
- Usar métodos modernos de ES6+
- Aplicar destructuring y spread operator
📖 Arrays Avanzados
Métodos Funcionales
const numeros = [1, 2, 3, 4, 5];
// map: transformar elementos
const duplicados = numeros.map(n => n * 2);
console.log(duplicados); // [2, 4, 6, 8, 10]
// filter: filtrar elementos
const pares = numeros.filter(n => n % 2 === 0);
console.log(pares); // [2, 4]
// reduce: acumular valores
const suma = numeros.reduce((acc, n) => acc + n, 0);
console.log(suma); // 15
// find: encontrar primer elemento que cumpla condición
const mayor3 = numeros.find(n => n > 3);
console.log(mayor3); // 4
// every: verificar si todos cumplen condición
const todosMenores10 = numeros.every(n => n < 10);
console.log(todosMenores10); // true
// some: verificar si alguno cumple condición
const algunMayor4 = numeros.some(n => n > 4);
console.log(algunMayor4); // true
Destructuring
const persona = {
nombre: "Ana",
edad: 25,
direccion: {
calle: "Main St",
numero: 123
}
};
// Desestructuración simple
const { nombre, edad } = persona;
console.log(nombre, edad); // "Ana", 25
// Desestructuración anidada
const { direccion: { calle, numero } } = persona;
console.log(calle, numero); // "Main St", 123
// Asignación con destructuring
let a, b;
[a, b] = [1, 2];
console.log(a, b); // 1, 2
// Parámetros de función con destructuring
function mostrarDatos({ nombre, edad }) {
console.log(`Nombre: ${nombre}, Edad: ${edad}`);
}
mostrarDatos(persona);
Spread y Rest Operator
// Spread operator (...)
const numeros = [1, 2, 3];
const masNumeros = [4, 5, ...numeros, 6];
console.log(masNumeros); // [4, 5, 1, 2, 3, 6]
// Clonar un objeto
const persona = { nombre: "Ana", edad: 25 };
const copiaPersona = { ...persona };
console.log(copiaPersona); // { nombre: "Ana", edad: 25 }
// Rest operator (...) en funciones
function sumarTodo(...numeros) {
return numeros.reduce((acum, n) => acum + n, 0);
}
console.log(sumarTodo(1, 2, 3, 4)); // 10
📖 Objetos Avanzados
Métodos y this
const persona = {
nombre: "Ana",
edad: 25,
saludar() {
return `Hola, soy ${this.nombre}`;
}
};
console.log(persona.saludar()); // "Hola, soy Ana"
// Problema común con setTimeout
setTimeout(function() {
console.log(this); // undefined (en modo estricto) o window (en navegador)
}, 1000);
// Solución con arrow function
setTimeout(() => {
console.log(this); // correcto, mantiene el contexto léxico
}, 1000);
Object.create y herencia
const prototipo = {
saludar() {
return `Hola, soy ${this.nombre}`;
}
};
const ana = Object.create(prototipo);
ana.nombre = "Ana";
ana.edad = 25;
console.log(ana.saludar()); // "Hola, soy Ana"
// Verificar herencia
console.log(ana.__proto__ === prototipo); // true
Módulo 6: DOM y Manipulación de Eventos
⏱️ Tiempo estimado: 8-9 horas
📋 Prerequisites
- Completar módulos anteriores
- Entender objetos y arrays
- Conocimientos básicos de HTML/CSS
- Familiaridad con funciones callback
🎯 Objetivos del Módulo
- Dominar la manipulación del DOM
- Implementar manejo eficiente de eventos
- Crear interfaces dinámicas e interactivas
- Optimizar operaciones del DOM
📖 El Document Object Model (DOM)
Selección de Elementos
// Métodos modernos de selección
const elemento = document.querySelector('.mi-clase');
const elementos = document.querySelectorAll('.mi-clase');
const porId = document.getElementById('mi-id');
const porClase = document.getElementsByClassName('mi-clase');
// Navegación por el DOM
const padre = elemento.parentElement;
const hijos = elemento.children;
const siguiente = elemento.nextElementSibling;
const anterior = elemento.previousElementSibling;
// Validación de elementos
console.log(elemento?.classList.contains('activo'));
Manipulación del DOM
// Crear elementos
const div = document.createElement('div');
div.className = 'contenedor';
div.innerHTML = `
<h2>Título Nuevo</h2>
<p>Contenido del párrafo</p>
`;
// Modificar elementos
div.classList.add('destacado');
div.classList.remove('oculto');
div.classList.toggle('visible');
div.setAttribute('data-id', '123');
div.style.backgroundColor = '#f0f0f0';
// Insertar elementos
document.body.appendChild(div);
elemento.insertAdjacentHTML('beforeend', '<span>Nuevo texto</span>');
// Eliminar elementos
elemento.remove();
padre.removeChild(hijo);
Eventos y Event Delegation
// Manejadores de eventos modernos
elemento.addEventListener('click', (e) => {
console.log('Click detectado', e.target);
});
// Event delegation eficiente
document.querySelector('.lista').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
console.log('Click en item:', e.target.textContent);
}
});
// Prevenir comportamiento por defecto
formulario.addEventListener('submit', (e) => {
e.preventDefault();
// Manejar envío del formulario
});
🏋️♂️ Ejercicios Prácticos
Ejercicio 1: Manipulación del DOM
// 1. Crear un elemento <div> con clase "contenedor"
// 2. Dentro del div, agregar un <h2> y un <p> con texto
// 3. Agregar el div al final del <body>
// Tu código aquí:
const contenedor = document.createElement('div');
contenedor.className = 'contenedor';
contenedor.innerHTML = `
<h2>Título del Contenedor</h2>
<p>Este es un párrafo dentro del contenedor.</p>
`;
document.body.appendChild(contenedor);
Ejercicio 2: Eventos
// HTML:
// <button id="mi-boton">Click me</button>
// <div id="resultado"></div>
// JavaScript:
// 1. Seleccionar el botón y el div de resultado
// 2. Agregar un evento 'click' al botón
// 3. En el manejador, cambiar el texto del div a "¡Hola, mundo!"
// Tu código aquí:
const boton = document.getElementById('mi-boton');
const resultado = document.getElementById('resultado');
boton.addEventListener('click', () => {
resultado.textContent = "¡Hola, mundo!";
});
Ejercicio 3: Event Delegation
// HTML:
// <ul class="lista">
// <li class="item">Item 1</li>
// <li class="item">Item 2</li>
// <li class="item">Item 3</li>
// </ul>
// JavaScript:
// 1. Agregar un evento 'click' a la lista
// 2. En el manejador, verificar si el elemento clickeado es un item
// 3. Si es un item, mostrar su texto en la consola
// Tu código aquí:
document.querySelector('.lista').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
console.log('Click en item:', e.target.textContent);
}
});
💡 Mini Proyecto: Galería de Imágenes
Crea una galería de imágenes que permita:
- Ver imágenes en miniatura
- Ampliar imagen al hacer clic
- Navegar entre imágenes ampliadas
<div class="galeria">
<img src="img1_thumb.jpg" data-full="img1.jpg" alt="Imagen 1">
<img src="img2_thumb.jpg" data-full="img2.jpg" alt="Imagen 2">
<img src="img3_thumb.jpg" data-full="img3.jpg" alt="Imagen 3">
</div>
<div id="modal" class="modal">
<span class="cerrar">×</span>
<img class="modal-contenido" id="img-ampliada">
<div id="caption"></div>
</div>
// Obtener elementos
const galeria = document.querySelector('.galeria');
const modal = document.getElementById('modal');
const imgAmpliada = document.getElementById('img-ampliada');
const caption = document.getElementById('caption');
// Manejar clic en imágenes de la galería
galeria.addEventListener('click', (e) => {
if (e.target.tagName === 'IMG') {
const src = e.target.dataset.full;
const alt = e.target.alt;
imgAmpliada.src = src;
caption.textContent = alt;
modal.style.display = "block";
}
});
// Cerrar modal
modal.querySelector('.cerrar').addEventListener('click', () => {
modal.style.display = "none";
});
Módulo 7: Asincronía y APIs
⏱️ Tiempo estimado: 8-9 horas
📋 Prerequisites
- Completar módulos 1-6
- Entender callbacks y eventos
- Conocer JSON y estructuras de datos
- Familiaridad con peticiones HTTP
🎯 Objetivos del Módulo
- Dominar programación asíncrona
- Trabajar con Promesas y async/await
- Consumir APIs REST
- Manejar errores en operaciones asíncronas
📖 Fundamentos de Asincronía
Callbacks
// Callback básico
function obtenerDatos(callback) {
setTimeout(() => {
const datos = { id: 1, nombre: "Ana" };
callback(null, datos);
}, 1000);
}
obtenerDatos((error, datos) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('Datos:', datos);
});
// Callback Hell (problema común)
obtenerUsuario(id, (error, usuario) => {
if (error) return console.error(error);
obtenerPosts(usuario.id, (error, posts) => {
if (error) return console.error(error);
obtenerComentarios(posts[0].id, (error, comentarios) => {
if (error) return console.error(error);
console.log(comentarios);
});
});
});
Promesas
// Crear una promesa
const promesa = new Promise((resolve, reject) => {
setTimeout(() => {
const exito = true;
if (exito) {
resolve({ id: 1, nombre: "Ana" });
} else {
reject(new Error("Algo salió mal"));
}
}, 1000);
});
// Usar promesas
promesa
.then(datos => console.log('Éxito:', datos))
.catch(error => console.error('Error:', error));
// Encadenar promesas
obtenerUsuario(id)
.then(usuario => obtenerPosts(usuario.id))
.then(posts => obtenerComentarios(posts[0].id))
.then(comentarios => console.log(comentarios))
.catch(error => console.error(error));
Async/Await
// Función asíncrona
async function obtenerDatos() {
try {
const usuario = await obtenerUsuario(1);
const posts = await obtenerPosts(usuario.id);
const comentarios = await obtenerComentarios(posts[0].id);
return comentarios;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Usar async/await
async function iniciar() {
try {
const resultado = await obtenerDatos();
console.log(resultado);
} catch (error) {
console.error(error);
}
}
// Ejecutar función asíncrona
iniciar();
📖 Trabajando con APIs
Fetch API
// GET request
fetch('https://api.ejemplo.com/usuarios')
.then(response => {
if (!response.ok) {
throw new Error('Error de red');
}
return response.json();
})
.then(datos => console.log(datos))
.catch(error => console.error('Error:', error));
// POST request
const nuevoUsuario = {
nombre: "Ana",
email: "ana@email.com"
};
fetch('https://api.ejemplo.com/usuarios', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(nuevoUsuario)
})
.then(response => response.json())
.then(usuario => console.log('Usuario creado:', usuario))
.catch(error => console.error('Error:', error));
Async/Await con Fetch
async function obtenerUsuarios() {
try {
const response = await fetch('https://api.ejemplo.com/usuarios');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const usuarios = await response.json();
return usuarios;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
🏋️♂️ Ejercicios Prácticos
Ejercicio 1: Promesas
// Implementar una función que simule una operación asíncrona
function esperarTiempo(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Esperé ${ms}ms`);
}, ms);
});
}
// Usar Promise.all para operaciones paralelas
Promise.all([
esperarTiempo(1000),
esperarTiempo(2000),
esperarTiempo(3000)
])
.then(resultados => console.log(resultados))
.catch(error => console.error(error));
Ejercicio 2: API REST
// Crear un cliente para una API REST
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async get(endpoint) {
const response = await fetch(`${this.baseURL}${endpoint}`);
if (!response.ok) throw new Error('Error en GET');
return response.json();
}
async post(endpoint, data) {
const response = await fetch(`${this.baseURL}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) throw new Error('Error en POST');
return response.json();
}
}
// Usar el cliente
const api = new APIClient('https://api.ejemplo.com');
api.get('/usuarios')
.then(usuarios => console.log(usuarios))
.catch(error => console.error(error));
💡 Mini Proyecto: Dashboard de Clima
class WeatherDashboard {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://api.weatherapi.com/v1';
}
async obtenerClima(ciudad) {
try {
const response = await fetch(
`${this.baseURL}/current.json?key=${this.apiKey}&q=${ciudad}`
);
if (!response.ok) {
throw new Error('Ciudad no encontrada');
}
const data = await response.json();
return this.formatearDatos(data);
} catch (error) {
console.error('Error:', error);
throw error;
}
}
formatearDatos(data) {
return {
ciudad: data.location.name,
pais: data.location.country,
temperatura: data.current.temp_c,
condicion: data.current.condition.text,
icono: data.current.condition.icon
};
}
async mostrarClima(ciudad) {
try {
const clima = await this.obtenerClima(ciudad);
this.actualizarUI(clima);
} catch (error) {
this.mostrarError(error.message);
}
}
actualizarUI(clima) {
const html = `
<div class="clima-card">
<h2>${clima.ciudad}, ${clima.pais}</h2>
<img src="${clima.icono}" alt="${clima.condicion}">
<p class="temperatura">${clima.temperatura}°C</p>
<p class="condicion">${clima.condicion}</p>
</div>
`;
document.getElementById('resultado').innerHTML = html;
}
mostrarError(mensaje) {
document.getElementById('resultado').innerHTML = `
<div class="error">
<p>❌ ${mensaje}</p>
</div>
`;
}
}
// Uso del dashboard
const dashboard = new WeatherDashboard('TU_API_KEY');
document.getElementById('buscar').addEventListener('click', () => {
const ciudad = document.getElementById('ciudad').value;
if (ciudad) {
dashboard.mostrarClima(ciudad);
}
});
Módulo 8: Patrones de Diseño y Buenas Prácticas
⏱️ Tiempo estimado: 8-9 horas
📋 Prerequisites
- Completar módulos 1-7
- Dominio de objetos y clases
- Experiencia con ES6+
- Conocimientos de programación orientada a objetos
🎯 Objetivos del Módulo
- Implementar patrones de diseño comunes
- Aplicar principios SOLID
- Optimizar rendimiento y mantenibilidad
- Escribir código modular y reutilizable
📖 Patrones de Diseño Fundamentales
Singleton Pattern
class ConfiguracionApp {
constructor() {
if (ConfiguracionApp.instancia) {
return ConfiguracionApp.instancia;
}
this.config = {
tema: 'claro',
idioma: 'es',
notificaciones: true
};
ConfiguracionApp.instancia = this;
}
getConfig() {
return this.config;
}
setConfig(key, value) {
this.config[key] = value;
}
}
// Uso
const config1 = new ConfiguracionApp();
const config2 = new ConfiguracionApp();
console.log(config1 === config2); // true
Factory Pattern
class Usuario {
constructor(tipo) {
this.tipo = tipo;
this.permisos = [];
}
}
class UsuarioFactory {
crearUsuario(tipo) {
const usuario = new Usuario(tipo);
switch(tipo) {
case 'admin':
usuario.permisos = ['crear', 'editar', 'eliminar', 'ver'];
break;
case 'editor':
usuario.permisos = ['editar', 'ver'];
break;
case 'visitante':
usuario.permisos = ['ver'];
break;
default:
throw new Error('Tipo de usuario no válido');
}
return usuario;
}
}
// Uso
const factory = new UsuarioFactory();
const admin = factory.crearUsuario('admin');
console.log(admin.permisos); // ['crear', 'editar', 'eliminar', 'ver']
Observer Pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event]
.filter(cb => cb !== callback);
}
}
}
// Uso
const notificador = new EventEmitter();
function mostrarNotificacion(mensaje) {
console.log(`Notificación: ${mensaje}`);
}
notificador.on('mensaje', mostrarNotificacion);
notificador.emit('mensaje', '¡Hola mundo!');
📖 Principios SOLID
Single Responsibility Principle
// ❌ Mal: Clase con múltiples responsabilidades
class Usuario {
constructor(nombre) {
this.nombre = nombre;
}
guardarEnDB() { /* ... */ }
generarReporte() { /* ... */ }
enviarEmail() { /* ... */ }
}
// ✅ Bien: Separar responsabilidades
class Usuario {
constructor(nombre) {
this.nombre = nombre;
}
}
class UsuarioRepository {
guardarUsuario(usuario) { /* ... */ }
}
class ReporteUsuario {
generarReporte(usuario) { /* ... */ }
}
class EmailService {
enviarEmail(destinatario, mensaje) { /* ... */ }
}
Open/Closed Principle
// ❌ Mal: Modificar clase existente para nuevas funcionalidades
class Calculadora {
calcular(num1, num2, operacion) {
switch(operacion) {
case 'suma': return num1 + num2;
case 'resta': return num1 - num2;
// Necesitamos modificar para agregar nuevas operaciones
}
}
}
// ✅ Bien: Extender sin modificar
class Operacion {
ejecutar(num1, num2) {
throw new Error('Método abstracto');
}
}
class Suma extends Operacion {
ejecutar(num1, num2) {
return num1 + num2;
}
}
class Resta extends Operacion {
ejecutar(num1, num2) {
return num1 - num2;
}
}
🏋️♂️ Ejercicios Prácticos
Ejercicio 1: Implementar un Sistema de Plugins
class PluginManager {
constructor() {
this.plugins = new Map();
}
registrar(nombre, plugin) {
if (this.plugins.has(nombre)) {
throw new Error(`Plugin ${nombre} ya existe`);
}
this.plugins.set(nombre, plugin);
}
ejecutar(nombre, ...args) {
const plugin = this.plugins.get(nombre);
if (!plugin) {
throw new Error(`Plugin ${nombre} no encontrado`);
}
return plugin.ejecutar(...args);
}
}
// Ejemplo de uso
class ValidadorPlugin {
ejecutar(datos) {
// Lógica de validación
return datos.length > 0;
}
}
class FormateadorPlugin {
ejecutar(texto) {
return texto.toLowerCase().trim();
}
}
const manager = new PluginManager();
manager.registrar('validador', new ValidadorPlugin());
manager.registrar('formateador', new FormateadorPlugin());
console.log(manager.ejecutar('formateador', ' HOLA MUNDO '));
💡 Mini Proyecto: Sistema de Carrito de Compras
// Interfaces
class PrecioStrategy {
calcular(productos) {
throw new Error('Método abstracto');
}
}
class PrecioRegular extends PrecioStrategy {
calcular(productos) {
return productos.reduce((total, p) => total + p.precio, 0);
}
}
class PrecioDescuento extends PrecioStrategy {
constructor(descuento) {
super();
this.descuento = descuento;
}
calcular(productos) {
const subtotal = productos.reduce((total, p) => total + p.precio, 0);
return subtotal * (1 - this.descuento);
}
}
// Implementación del carrito
class CarritoCompras {
constructor(precioStrategy) {
this.productos = [];
this.precioStrategy = precioStrategy;
this.observers = [];
}
agregarProducto(producto) {
this.productos.push(producto);
this.notificarObservers('productoAgregado', producto);
}
eliminarProducto(id) {
const index = this.productos.findIndex(p => p.id === id);
if (index > -1) {
const producto = this.productos.splice(index, 1)[0];
this.notificarObservers('productoEliminado', producto);
}
}
calcularTotal() {
return this.precioStrategy.calcular(this.productos);
}
setPrecioStrategy(strategy) {
this.precioStrategy = strategy;
}
addObserver(observer) {
this.observers.push(observer);
}
notificarObservers(evento, data) {
this.observers.forEach(observer => observer.actualizar(evento, data));
}
}
// Observer para UI
class CarritoUI {
actualizar(evento, data) {
switch(evento) {
case 'productoAgregado':
console.log(`Agregado: ${data.nombre} - $${data.precio}`);
break;
case 'productoEliminado':
console.log(`Eliminado: ${data.nombre}`);
break;
}
}
}
// Ejemplo de uso
const carrito = new CarritoCompras(new PrecioRegular());
carrito.addObserver(new CarritoUI());
carrito.agregarProducto({ id: 1, nombre: 'Producto 1', precio: 100 });
carrito.agregarProducto({ id: 2, nombre: 'Producto 2', precio: 200 });
console.log('Total Regular:', carrito.calcularTotal());
carrito.setPrecioStrategy(new PrecioDescuento(0.1)); // 10% descuento
console.log('Total con Descuento:', carrito.calcularTotal());
Has terminado el curso? 🎉
Para profundizar en javascript puedes seguir el siguiente nivel de curso: nivel avanzado del curso