Módulo 01 // Unidad 06

Almacenamiento en el navegador

Conservar datos entre visitas sin servidor: localStorage, sessionStorage y JSON

Unidad para alumnos del curso

Comprobando tu sesión…

Inicia sesión con la cuenta de Google con la que estás matriculado para ver esta unidad.

Esa cuenta no tiene acceso a este curso. Prueba con otra.

No se ha podido cargar el inicio de sesión de Google. Comprueba la conexión o si algún bloqueador lo impide, y recarga la página.

Todo lo que guardas en variables desaparece al recargar la página. Para que una aplicación recuerde el tema elegido, el carrito o las tareas de la sesión anterior sin montar un servidor, el navegador ofrece un almacén clave-valor por cada web. Es sencillo de usar, pero tiene reglas claras sobre qué guarda, cuánto dura y quién puede leerlo.

1 // localStorage y sessionStorage

Las dos APIs tienen exactamente los mismos métodos. Cambia cuánto tiempo viven los datos:

localStorage.setItem('tema', 'oscuro');   // guardar
localStorage.getItem('tema');             // 'oscuro'
localStorage.getItem('no-existe');        // null
localStorage.removeItem('tema');          // borrar una clave
localStorage.clear();                     // borrar todo lo de esta web
localStorage sessionStorage
Duración Hasta que se borre Hasta cerrar la pestaña
Compartido entre pestañas Sí, de la misma web No, cada pestaña el suyo
Uso típico Preferencias, borradores, datos de una app sin servidor Pasos de un formulario, estado temporal

Las dos son síncronas: getItem devuelve el valor directamente, sin callbacks ni esperas. Tienen además un límite de tamaño de unos pocos megabytes por web, más que suficiente para preferencias o listas, pero no para ficheros o grandes volúmenes de datos.

2 // Sólo textos: el papel de JSON

La limitación que más errores provoca es que las claves y los valores son siempre textos. Si guardas un número, recuperas un texto; si guardas un objeto, se convierte en '[object Object]' y los datos se pierden.

La solución es JSON (JavaScript Object Notation), un formato de texto que representa objetos, arrays, números, textos, booleanos y null. JavaScript trae dos funciones para pasar de un lado a otro:

const preferencias = { tema: 'oscuro', tamano: 16, avisos: true };

const texto = JSON.stringify(preferencias);
// '{"tema":"oscuro","tamano":16,"avisos":true}'

localStorage.setItem('preferencias', texto);

const leido = JSON.parse(localStorage.getItem('preferencias'));
leido.tamano + 2;   // 18: vuelve a ser un número
GUARDAR Y RECUPERAR DATOS CON LOCALSTORAGE localStorage de https://mi-web.es clave 'preferencias' valor (texto) '{"tema":"oscuro"}' sólo textos persiste al cerrar GUARDAR { tema: 'oscuro' } JSON.stringify setItem RECUPERAR (otra visita) getItem JSON.parse { tema: 'oscuro' } Sin JSON.stringify, un objeto se guardaría como el texto '[object Object]'.
Mira la caja del almacén: el valor está entre comillas, porque es un texto. Las dos conversiones, a la ida y a la vuelta, son las que permiten guardar un objeto y recuperarlo tal cual.

JSON no puede representarlo todo. Las funciones y los valores undefined desaparecen, las fechas se convierten en textos y los objetos pierden su clase. Para datos sencillos, como los de esta unidad, no es ningún problema.

JSON.parse lanza un error si el texto no es JSON válido, y la primera vez que el usuario visita la web getItem devuelve null. Lee siempre el almacenamiento dentro de una función que contemple los dos casos y devuelva un valor por defecto.

3 // Quién puede leer los datos

Cada almacenamiento pertenece a un origen: la combinación de protocolo, dominio y puerto. Los datos de https://mi-web.es no son visibles desde https://otra-web.es, ni siquiera desde http://mi-web.es. Durante el desarrollo, localhost con distinto puerto también cuenta como origen distinto, así que una app en el puerto 5173 no ve lo que guardó otra en el 3000.

Dentro del mismo origen, en cambio, cualquier script de la página puede leerlo todo, incluidos los scripts de terceros que cargue la web.

Guarda en el navegador sólo lo que no pasaría nada si leyera otra persona con acceso al dispositivo o un script ajeno: preferencias, borradores, datos de interfaz. Nunca contraseñas ni datos personales sensibles. Y recuerda que el usuario puede borrarlo todo en cualquier momento, así que tu aplicación debe funcionar si no encuentra nada.

Para completar el mapa: las cookies también guardan datos en el navegador, pero su propósito es otro. Se envían automáticamente al servidor con cada petición y se usan sobre todo para mantener sesiones. Y para grandes volúmenes de datos estructurados existe IndexedDB, una base de datos asíncrona integrada en el navegador.

La lista de tareas de las unidades anteriores debe conservarse al recargar. El array tareas es la fuente de verdad y pintar(tareas) ya dibuja la lista.

const CLAVE = 'tareas:v1';

function cargarTareas() {
  try {
    return JSON.parse(localStorage.getItem(CLAVE)) ?? [];
  } catch {
    return [];
  }
}

function guardarTareas(tareas) {
  localStorage.setItem(CLAVE, JSON.stringify(tareas));
}

let tareas = cargarTareas();
pintar(tareas);
  1. Una clave con versión: tareas:v1 permite cambiar el formato en el futuro sin leer datos antiguos incompatibles.
  2. Primera visita: getItem devuelve null, JSON.parse(null) devuelve null y ?? [] lo sustituye por un array vacío.
  3. Datos corruptos: si alguien editó el valor a mano y no es JSON válido, JSON.parse lanza un error y el catch devuelve también un array vacío.
  4. Guardar tras cada cambio: en el listener delegado que marca una tarea como hecha, se añade guardarTareas(tareas) justo antes de pintar(tareas).
Las tareas sobreviven a recargas y a cerrar el navegador, y la aplicación arranca bien aunque no haya datos o estén estropeados. El flujo completo queda así: los datos viven en un array, cada cambio se guarda y se repinta, y al cargar se lee el almacén una sola vez.

4 // Para practicar

Guarda preferencias y datos entre visitas, y comprueba qué ocurre cuando lo guardado no existe o está roto. Cada ejercicio trae el enunciado en los comentarios del código.

Preferencias y contador de visitastraining-daw-sv/modulo-01/06-almacenamiento-en-el-navegador/01_theme_preferenceLista de tareas persistentetraining-daw-sv/modulo-01/06-almacenamiento-en-el-navegador/02_persistent_tasks

Autoevaluación

Guardas localStorage.setItem('usuario', { nombre: 'Ana' }) y al leerlo obtienes '[object Object]'. ¿Qué ha pasado?
localStorage sólo guarda textos, así que convirtió el objeto a texto de la forma por defecto. Esa conversión de un objeto cualquiera da '[object Object]' y los datos se pierden. Hay que serializarlo antes con JSON.stringify y reconstruirlo al leer con JSON.parse.
Una aplicación guarda las preferencias en sessionStorage. El usuario cierra la pestaña, abre la web de nuevo y las preferencias han desaparecido. ¿Es un fallo?
No: es exactamente lo que hace sessionStorage. Sus datos pertenecen a la pestaña y se borran al cerrarla. Para que sobrevivan entre visitas hay que usar localStorage, que persiste hasta que se borre explícitamente.
¿Por qué no es seguro guardar un token de sesión o datos personales sensibles en localStorage?
Porque cualquier script que se ejecute en la página puede leerlo. Si la web sufre una inyección de código (XSS) o carga un script de terceros comprometido, ese código tiene acceso completo a localStorage. Además, los datos quedan en texto plano en el disco del dispositivo.