1. ¿Qué es Web Storage?
Web Storage proporciona dos mecanismos para almacenar datos clave-valor en el navegador
del usuario: localStorage y sessionStorage.
Diferencias Clave
| Aspecto | localStorage | sessionStorage |
|---|---|---|
| Persistencia | Permanente (hasta borrar) | Mientras dure la pestaña |
| Alcance | Todas las pestañas del mismo origen | Solo la pestaña actual |
| Capacidad | ~5-10 MB | ~5-10 MB |
| Expira | ? Nunca | ? Al cerrar pestaña |
Ventajas y Limitaciones
- ? Fácil de usar (API -Application Programming Interface, reglas de comunicación entre aplicaciones- simple)
- ? No requiere servidor
- ? Sincrónico (más rápido para datos pequeños)
- ? Solo almacena strings (debes serializar objetos)
- ? 5-10 MB de límite
- ? No es seguro para datos sensibles
2. localStorage - Métodos Básicos
setItem() - Guardar Datos
// Guardar un valor
localStorage.setItem('nombre', 'Carlos');
localStorage.setItem('edad', '28');
localStorage.setItem('tema', 'oscuro');
getItem() - Obtener Datos
const nombre = localStorage.getItem('nombre');
console.log(nombre); // "Carlos"
const edad = localStorage.getItem('edad');
console.log(edad); // "28" (es string!)
// Si no existe, devuelve null
const ciudad = localStorage.getItem('ciudad');
console.log(ciudad); // null
removeItem() - Eliminar un Dato
localStorage.removeItem('edad');
const edad = localStorage.getItem('edad');
console.log(edad); // null (ya no existe)
clear() - Borrar Todo
// ?? Borra TODO el localStorage de este dominio
localStorage.clear();
console.log(localStorage.getItem('nombre')); // null
Atajo con Notación de Objeto
// También puedes usar notación de objeto (menos recomendado)
localStorage.nombre = 'Ana';
localStorage['edad'] = 25;
console.log(localStorage.nombre); // "Ana"
delete localStorage.edad;
3. Almacenar Objetos y Arrays
El storage solo acepta strings. Para objetos y arrays, usa JSON.stringify() y
JSON.parse().
Guardar un Objeto
const usuario = {
nombre: 'Laura',
edad: 30,
email: 'laura@example.com',
preferencias: {
tema: 'oscuro',
idioma: 'es'
}
};
// ? Convertir a JSON string antes de guardar
localStorage.setItem('usuario', JSON.stringify(usuario));
Recuperar el Objeto
const usuarioJSON = localStorage.getItem('usuario');
if (usuarioJSON) {
const usuario = JSON.parse(usuarioJSON);
console.log(usuario.nombre); // "Laura"
console.log(usuario.preferencias.tema); // "oscuro"
} else {
console.log('No hay usuario guardado');
}
Guardar un Array
const tareas = [
{ id: 1, texto: 'Comprar leche', completada: false },
{ id: 2, texto: 'Estudiar JS', completada: true }
];
localStorage.setItem('tareas', JSON.stringify(tareas));
// Recuperar
const tareasJSON = localStorage.getItem('tareas');
if (tareasJSON) {
const tareas = JSON.parse(tareasJSON);
console.log(tareas.length); // 2
console.log(tareas[0].texto); // "Comprar leche"
}
Helper Functions (Utilidades)
// Funciones helper para simplificar
const storage = {
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get(key, defaultValue = null) {
const item = localStorage.getItem(key);
if (item === null) return defaultValue;
try {
return JSON.parse(item);
} catch (error) {
return item; // Si no es JSON, devolver como string
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
}
};
// Uso
storage.set('usuario', { nombre: 'Pedro', edad: 25 });
const usuario = storage.get('usuario');
console.log(usuario.nombre); // "Pedro"
const config = storage.get('config', { tema: 'claro' }); // default
console.log(config);
4. sessionStorage
Funciona exactamente igual que localStorage, pero los datos se borran al cerrar la
pestaña/ventana.
Uso Idéntico
// Guardar
sessionStorage.setItem('tokenTemporal', 'abc123');
sessionStorage.setItem('paginaActual', JSON.stringify({ ruta: '/home' }));
// Obtener
const token = sessionStorage.getItem('tokenTemporal');
console.log(token); // "abc123"
// Eliminar
sessionStorage.removeItem('tokenTemporal');
// Borrar todo
sessionStorage.clear();
Cuándo Usar Cada Uno
| Usa localStorage para: | Usa sessionStorage para: |
|---|---|
| Preferencias de usuario | Datos de formularios en progreso |
| Tema (claro/oscuro) | Estado temporal de navegación |
| Idioma seleccionado | Carrito de compras temporal |
| Tokens de auth (con cuidado) | Paginación/filtros temporales |
| Favoritos, historial | Wizard/paso a paso |
5. Verificar Disponibilidad de Storage
Algunos navegadores en modo privado pueden bloquear storage:
function storageDisponible(tipo) {
try {
const storage = window[tipo];
const test = '__storage_test__';
storage.setItem(test, test);
storage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
if (storageDisponible('localStorage')) {
console.log('localStorage disponible');
} else {
console.log('localStorage NO disponible (modo privado?)');
}
if (storageDisponible('sessionStorage')) {
console.log('sessionStorage disponible');
}
6. Escuchar Cambios (storage event)
Si cambias el localStorage en una pestaña, otras pestañas del mismo dominio pueden detectarlo:
// En pestaña A y B, escuchar cambios
window.addEventListener('storage', (e) => {
console.log('Storage cambió!');
console.log('Clave:', e.key);
console.log('Valor antiguo:', e.oldValue);
console.log('Valor nuevo:', e.newValue);
console.log('URL:', e.url);
// Ejemplo: actualizar UI si cambia el tema
if (e.key === 'tema') {
document.body.className = e.newValue; // aplicar nuevo tema
}
});
// En pestaña B, cambiar el tema
localStorage.setItem('tema', 'oscuro');
// La pestaña A detectará el cambio automáticamente
3. IndexedDB (La Base de Datos)
Para aplicaciones que manejen muchos datos (megas o gigas) de forma asíncrona. No se suele explicar en cursos básicos por su complejidad.
🏆 Gran Comparativa de Almacenamiento
¿Cuándo usar cada cosa? Aquí tienes la guía definitiva:
| Característica | Storage (Local/Session) | Cookies | IndexedDB |
|---|---|---|---|
| Capacidad | 5 - 10 MB | ~4 KB (muy pequeña) | Casi ilimitada |
| Sencillez | Extrema (Clave: Valor) | Compleja (Gestión de texto) | Alta (Asíncrona/Eventos) |
| Envío al Servidor | No | Sí (en cada petición HTTP) | No |
| Uso principal | Preferencias y Estado UI | Sesiones y Rastreo | Offline Apps / Grandes datos |
| Seguridad | Baja (Acceso por JS) | Alta (con HttpOnly) | Baja (Acceso por JS) |
7. Ejemplo Real: Sistema de Preferencias
// Clase para manejar preferencias de usuario
class PreferenciasUsuario {
constructor() {
this.STORAGE_KEY = 'preferencias';
this.defaults = {
tema: 'claro',
idioma: 'es',
notificaciones: true,
volumen: 50
};
}
// Obtener todas las preferencias
obtener() {
const guardado = localStorage.getItem(this.STORAGE_KEY);
if (!guardado) return { ...this.defaults };
try {
return { ...this.defaults, ...JSON.parse(guardado) };
} catch (error) {
console.error('Error al parsear preferencias');
return { ...this.defaults };
}
}
// Obtener una preferencia específica
obtenerPref(clave) {
const prefs = this.obtener();
return prefs[clave];
}
// Guardar preferencias (merge con existentes)
guardar(nuevasPrefs) {
const prefsActuales = this.obtener();
const prefsActualizadas = { ...prefsActuales, ...nuevasPrefs };
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(prefsActualizadas));
return prefsActualizadas;
}
// Restablecer a valores por defecto
restablecer() {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.defaults));
return this.defaults;
}
// Aplicar tema al DOM
aplicarTema() {
const tema = this.obtenerPref('tema');
document.body.classList.toggle('tema-oscuro', tema === 'oscuro');
}
}
// Uso
const prefs = new PreferenciasUsuario();
// Al cargar la página
document.addEventListener('DOMContentLoaded', () => {
prefs.aplicarTema();
const config = prefs.obtener();
console.log('Configuración actual:', config);
});
// Cambiar tema
function cambiarTema(nuevoTema) {
prefs.guardar({ tema: nuevoTema });
prefs.aplicarTema();
}
// Botón de toggle
document.getElementById('btnTema')?.addEventListener('click', () => {
const temaActual = prefs.obtenerPref('tema');
const nuevoTema = temaActual === 'claro' ? 'oscuro' : 'claro';
cambiarTema(nuevoTema);
});
8. Ejemplo Real: TODO List Persistente
class TodoList {
constructor() {
this.STORAGE_KEY = 'todos';
this.todos = this.cargar();
}
cargar() {
const data = localStorage.getItem(this.STORAGE_KEY);
return data ? JSON.parse(data) : [];
}
guardar() {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.todos));
}
agregar(texto) {
const todo = {
id: Date.now(),
texto,
completada: false,
fecha: new Date().toISOString()
};
this.todos.push(todo);
this.guardar();
return todo;
}
toggleCompletada(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completada = !todo.completada;
this.guardar();
}
}
eliminar(id) {
this.todos = this.todos.filter(t => t.id !== id);
this.guardar();
}
obtenerTodos() {
return this.todos;
}
obtenerPendientes() {
return this.todos.filter(t => !t.completada);
}
limpiarCompletadas() {
this.todos = this.todos.filter(t => !t.completada);
this.guardar();
}
}
// Uso
const todoList = new TodoList();
// Agregar tarea
todoList.agregar('Estudiar JavaScript');
todoList.agregar('Hacer ejercicios');
// Obtener todas
console.log('Tareas:', todoList.obtenerTodos());
// Completar una tarea
const primeraId = todoList.obtenerTodos()[0].id;
todoList.toggleCompletada(primeraId);
// Ver pendientes
console.log('Pendientes:', todoList.obtenerPendientes());
// Los datos persisten al recargar la página!
9. Seguridad y Mejores Prácticas
?? NO Guardes Información Sensible
// ? NUNCA hagas esto
localStorage.setItem('contraseña', '12345');
localStorage.setItem('tarjetaCredito', '1234-5678-9012');
localStorage.setItem('tokenBancario', 'abc123');
// ? Storage es para datos NO sensibles
localStorage.setItem('tema', 'oscuro');
localStorage.setItem('idioma', 'es');
Validar Datos al Recuperar
function obtenerUsuarioSeguro() {
const data = localStorage.getItem('usuario');
if (!data) return null;
try {
const usuario = JSON.parse(data);
// Validar estructura
if (!usuario.nombre || !usuario.email) {
console.warn('Datos de usuario inválidos');
localStorage.removeItem('usuario');
return null;
}
return usuario;
} catch (error) {
console.error('Error al parsear usuario:', error);
localStorage.removeItem('usuario');
return null;
}
}
Manejar Cuotas Excedidas
function guardarSeguro(clave, valor) {
try {
localStorage.setItem(clave, JSON.stringify(valor));
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.error('Storage lleno!');
// Opción: limpiar datos antiguos
localStorage.clear();
// Reintentar
try {
localStorage.setItem(clave, JSON.stringify(valor));
return true;
} catch (e2) {
return false;
}
}
return false;
}
}
Expiración Manual de Datos
// Guardar con timestamp de expiración
function guardarConExpiracion(clave, valor, diasExpiracion) {
const ahora = new Date();
const item = {
valor: valor,
expira: ahora.getTime() + (diasExpiracion * 24 * 60 * 60 * 1000)
};
localStorage.setItem(clave, JSON.stringify(item));
}
// Recuperar verificando expiración
function obtenerSiNoExpirado(clave) {
const itemJSON = localStorage.getItem(clave);
if (!itemJSON) return null;
const item = JSON.parse(itemJSON);
const ahora = new Date();
if (ahora.getTime() > item.expira) {
// Expirado, eliminar
localStorage.removeItem(clave);
return null;
}
return item.valor;
}
// Uso
guardarConExpiracion('tokenTemporal', 'abc123', 7); // Expira en 7 días
const token = obtenerSiNoExpirado('tokenTemporal');
10. Ejercicios Prácticos
Ejercicio 1: Contador de Visitas
Crea un contador que incremente cada vez que se carga la página:
// 1. Obtener contador actual (o 0 si no existe)
// 2. Incrementar en 1
// 3. Guardar nuevo valor
// 4. Mostrar en consola: "Has visitado esta página X veces"
Ejercicio 2: Carrito de Compras
Implementa funciones básicas de carrito:
// agregarAlCarrito(producto)
// obtenerCarrito()
// eliminarDelCarrito(id)
// calcularTotal()
// Los productos deben persistir al recargar
Ejercicio 3: Formulario con Auto-guardado
Guarda el contenido de un input automáticamente:
// 1. Escuchar evento 'input' en un campo de texto
// 2. Guardar el valor en localStorage cada vez que cambie
// 3. Al cargar, restaurar el valor guardado
Resumen
- ?
localStoragepersiste datos permanentemente - ?
sessionStoragesolo dura mientras la pestaña esté abierta - ? Solo almacenan strings (usa JSON.stringify/parse para objetos)
- ? Límite de ~5-10 MB por dominio
- ? API simple: setItem, getItem, removeItem, clear
- ? NO guardes datos sensibles (contraseñas, tokens bancarios)
- ? Ideal para preferencias, temas, datos temporales