Los objetos de almacenaje web localStorage
y sessionStorage
permiten guardar pares de clave/valor en el navegador.
Lo que es interesante sobre ellos es que los datos sobreviven a una recarga de página (en el caso de sessionStorage
) y hasta un reinicio completo de navegador (en el caso de localStorage
). Lo veremos en breve.
Ya tenemos cookies. ¿Por qué tener objetos adicionales?
- Al contrario que las cookies, los objetos de almacenaje web no se envían al servidor en cada petición. Debido a esto, podemos almacenar mucha más información. La mayoría de los navegadores modernos permiten almacenar, como mínimo, 5 megabytes de datos y tienen opciones para configurar estos límites.
- También diferente de las cookies es que el servidor no puede manipular los objetos de almacenaje via cabeceras HTTP, todo se hace via JavaScript.
- El almacenaje está vinculado al orígen (al triplete dominio/protocolo/puerto). Esto significa que distintos protocolos o subdominios tienen distintos objetos de almacenaje, no pueden acceder a otros datos que no sean los suyos.
Ambos objetos de almacenaje proveen los mismos métodos y propiedades:
setItem(clave, valor)
– almacenar un par clave/valor.getItem(clave)
– obtener el valor por medio de la clave.removeItem(clave)
– eliminar la clave y su valor.clear()
– borrar todo.key(índice)
– obtener la clave de una posición dada.length
– el número de ítems almacenados.
Como puedes ver, es como una colección Map
(setItem/getItem/removeItem
), pero también permite el acceso a través de index con key(index)
.
Vamos a ver cómo funciona.
Demo de localStorage
Las principales funcionalidades de localStorage
son:
- Es compartido entre todas las pestañas y ventanas del mismo origen.
- Los datos no expiran. Persisten a los reinicios de navegador y hasta del sistema operativo.
Por ejemplo, si ejecutas éste código…
localStorage.setItem('test', 1);
… y cierras/abres el navegador, o simplemente abres la misma página en otra ventana, puedes coger el ítem que hemos guardado de este modo:
alert( localStorage.getItem('test') ); // 1
Solo tenemos que estar en el mismo dominio/puerto/protocolo, la url puede ser distinta.
localStorage
es compartido por toda las ventanas del mismo origen, de modo que si guardamos datos en una ventana, el cambio es visible en la otra.
Acceso tipo Objeto
También podemos utilizar un modo de acceder/guardar claves del mismo modo que se hace con objetos, así:
// guarda una clave
localStorage.test = 2;
// coge una clave
alert( localStorage.test ); // 2
// borra una clave
delete localStorage.test;
Esto se permite por razones históricas, y principalmente funciona, pero en general no se recomienda por dos motivos:
-
Si la clave es generada por el usuario, puede ser cualquier cosa, como
length
otoString
, u otro método propio delocalStorage
. En este casogetItem/setItem
funcionan correctamente, pero el acceso de simil-objeto falla;let key = 'length'; localStorage[key] = 5; // Error, no se puede asignar 'length'
-
Existe un evento
storage
, que se dispara cuando modificamos los datos. Este evento no se dispara si utilizamos el acceso tipo objeto. Lo veremos más tarde en este capítulo.
Iterando sobre las claves
Los métodos proporcionan la funcionalidad get / set / remove. ¿Pero cómo conseguimos todas las claves o valores guardados?
Desafortunadamente, los objetos de almacenaje no son iterables.
Una opción es utilizar iteración sobre un array:
for(let i=0; i<localStorage.length; i++) {
let key = localStorage.key(i);
alert(`${key}: ${localStorage.getItem(key)}`);
}
Otra opción es utilizar el loop específico para objetos for key in localStorage
tal como hacemos en objetos comunes.
Esta opción itera sobre las claves, pero también devuelve campos propios de localStorage
que no necesitamos:
// mal intento
for(let key in localStorage) {
alert(key); // muestra getItem, setItem y otros campos que no nos interesan
}
… De modo que necesitamos o bien filtrar campos des del prototipo con la validación hasOwnProperty
:
for(let key in localStorage) {
if (!localStorage.hasOwnProperty(key)) {
continue; // se salta claves como "setItem", "getItem" etc
}
alert(`${key}: ${localStorage.getItem(key)}`);
}
… O simplemente acceder a las claves “propias” con Object.keys
y iterar sobre éstas si es necesario:
let keys = Object.keys(localStorage);
for(let key of keys) {
alert(`${key}: ${localStorage.getItem(key)}`);
}
Esta última opción funciona, ya que Object.keys
solo devuelve las claves que pertenecen al objeto, ignorando el prototipo.
Solo strings
Hay que tener en cuenta que tanto la clave como el valor deben ser strings.
Si fueran de cualquier otro tipo, como un número o un objeto, se convertirían a cadena de texto automáticamente:
localStorage.user = {name: "John"};
alert(localStorage.user); // [object Object]
A pesar de eso, podemos utilizar JSON
para almacenar objetos:
localStorage.user = JSON.stringify({name: "John"});
// en algún momento más tarde
let user = JSON.parse( localStorage.user );
alert( user.name ); // John
También es posible pasar a texto todo el objeto de almacenaje, por ejemplo para debugear:
// se ha añadido opciones de formato a JSON.stringify para que el objeto se lea mejor
alert( JSON.stringify(localStorage, null, 2) );
sessionStorage
El objeto sessionStorage
se utiliza mucho menos que localStorage
.
Las propiedades y métodos son los mismos, pero es mucho más limitado:
sessionStorage
solo existe dentro de la pestaña actual del navegador.- Otra pestaña con la misma página tendrá un almacenaje distinto.
- Pero se comparte entre iframes en la pestaña (asumiendo que tengan el mismo orígen).
- Los datos sobreviven un refresco de página, pero no cerrar/abrir la pestaña.
Vamos a verlo en acción.
Ejecuta éste código…
sessionStorage.setItem('test', 1);
… Y recarga la página. Aún puedes acceder a los datos:
alert( sessionStorage.getItem('test') ); // después de la recarga: 1
… Pero si abres la misma página en otra pestaña, y lo intentas de nuevo, el código anterior devuelve null
, que significa que no se ha encontrado nada.
Esto es exactamente porque sessionStorage
no está vinculado solamente al orígen, sino también a la pestaña del navegador. Por ésta razón sessionStorage
se usa relativamente poco.
Evento storage
Cuando los datos se actualizan en localStorage
o en sessionStorage
, se dispara el evento storage con las propiedades:
key
– la clave que ha cambiado, (null
si se llama.clear()
).oldValue
– el anterior valor (null
si se añade una clave).newValue
– el nuevo valor (null
si se borra una clave).url
– la url del documento donde ha pasado la actualización.storageArea
– bien el objetolocalStorage
osessionStorage
, donde se ha producido la actualización.
El hecho importante es: el evento se dispara en todos los objetos window
donde el almacenaje es accesible, excepto en el que lo ha causado.
Vamos a desarrollarlo.
Imagina que tienes dos ventanas con el mismo sitio en cada una, de modo que localStorage
es compartido entre ellas.
Quizá quieras abrir ésta página en dos ventanas distintas para probar el código que sigue.
Si ambas ventanas están escuchando el evento window.onstorage
, cada una reaccionará a las actualizaciones que pasen en la otra.
// se dispara en actualizaciones hechas en el mismo almacenaje, desde otros documentos
window.onstorage = event => { // también puede usar window.addEventListener('storage', event => {
if (event.key != 'now') return;
alert(event.key + ':' + event.newValue + " at " + event.url);
};
localStorage.setItem('now', Date.now());
Hay que tener en cuenta que el evento también contiene: event.url
– la url del documento en que se actualizaron los datos.
También que event.storageArea
contiene el objeto de almacenaje – el evento es el mismo para sessionStorage
y localStorage
--, de modo que storageArea
referencia el que se modificó. Podemos hasta querer cambiar datos en él, para “responder” a un cambio.
Esto permite que distintas ventanas del mismo orígen puedan intercambiar mensajes.
Los navegadores modernos también soportan la API de Broadcast channel API, la API específica para la comunicación entre ventanas del mismo orígen. Es más completa, pero tiene menos soporte. Hay librerías que añaden polyfills para ésta API basados en localStorage
para que se pueda utilizar en cualquier entorno.
Resumen
Los objetos de almacenaje web localStorage
y sessionStorage
permiten guardar pares de clave/valor en el navegador.
- Tanto la
clave
como elvalor
deben ser strings. - El límite es de más de 5mb+, dependiendo del navegador.
- No expiran.
- Los datos están vinculados al origen (dominio/puerto/protocolo).
localStorage |
sessionStorage |
---|---|
Compartida entre todas las pestañas y ventanas que tengan el mismo orígen | Accesible en una pestaña del navegador, incluyendo iframes del mismo origen |
Sobrevive a reinicios del navegador | Muere al cerrar la pestaña |
API:
setItem(clave, valor)
– guarda pares clave/valor.getItem(clave)
– coge el valor de una clave.removeItem(clave)
– borra una clave con su valor.clear()
– borra todo.key(índice)
– coge la clave en una posición determinada.length
– el número de ítems almacenados.- Utiliza
Object.keys
para conseguir todas las claves. - Puede utilizar las claves como propiedades de objetor, pero en ese caso el evento
storage
no se dispara
Evento storage:
- Se dispara en las llamadas a
setItem
,removeItem
,clear
. - Contiene todos los datos relativos a la operación (
key/oldValue/newValue
), laurl
del documento y el objeto de almacenaje. - Se dispara en todos los objetos
window
que tienen acceso al almacenaje excepto el que ha generado el evento (en una pestaña en el caso desessionStorage
o globalmente en el caso delocalStorage
).