Objectes storage
L'objecte Storage de JavaScript, accessible mitjançant localStorage i sessionStorage, forma part de l'API Web Storage i s'utilitza per emmagatzemar dades clau-valor localment al navegador de l'usuari. És una alternativa més moderna i amb més capacitat que les cookies per a l'emmagatzematge de dades persistents de la sessió o a llarg termini.
Tipus d'emmagatzematge web
Hi ha dos tipus d'objectes Storage, que comparteixen les mateixes propietats i mètodes, però difereixen en la seva persistència:
localStorage (Emmagatzematge persistent)
-
Persistència: Les dades es mantenen fins que l'usuari les esborra manualment (a través de la configuració del navegador o el codi). Romanen disponibles fins i tot després de tancar i reobrir el navegador.
-
Cas d'Ús: Preferències d'usuari (tema fosc/clar), tokens d'autenticació (amb precaució), dades offline, historial de carrets de compra.
sessionStorage (Emmagatzematge de sessió)
-
Persistència: Les dades es mantenen només durant la sessió de la pestanya o finestra actual. Quan l'usuari tanca la pestanya o la finestra, les dades s'esborren automàticament.
-
Cas d'Ús: Dades temporals de la sessió, estat de formularis incomplets, estat de la navegació en una sessió.
Propietats i mètodes
Tant localStorage com sessionStorage implementen la mateixa interfície Storage. Totes les dades s'emmagatzemen com a parells clau-valor de tipus string.
Propietats
| Propietat | Descripció | Exemple |
length |
Retorna el nombre de parells clau-valor emmagatzemats a l'objecte Storage actual. |
3 |
Mètodes
| Mètode | Descripció |
setItem(clau, valor) |
Afegeix el parell clau-valor a l'emmagatzematge. Si la clau ja existeix, el valor antic es sobreescriu. |
getItem(clau) |
Retorna el valor associat a la clau especificada. Retorna null si la clau no es troba. |
removeItem(clau) |
Elimina el parell clau-valor amb la clau especificada de l'emmagatzematge. |
clear() |
Elimina tots els parells clau-valor de l'objecte Storage (tot el localStorage o tot el sessionStorage). |
key(index) |
Retorna la clau de l'índex especificat (útil per iterar sobre totes les dades). |
Exemples d'ús
Emmagatzemar dades simples (Strings)
Totes les dades s'emmagatzemen com a strings.
// Utilitzem localStorage
localStorage.setItem('usuari_nom', 'Jordi');
localStorage.setItem('data_login', '2025-09-30');
// Recuperar un valor
const nom = localStorage.getItem('usuari_nom'); // Resultat: "Jordi"
// Eliminar un element
localStorage.removeItem('data_login');
// Comprovar la mida
console.log(localStorage.length); // Resultat: 1 (només queda 'usuari_nom')
2. Emmagatzemar objectes o arrays (Necessita Serialització)
Com que Storage només accepta strings, cal convertir els objectes JavaScript a format JSON (string) abans de guardar-los i des-serialitzar-los quan es recuperen.
const configuracio = {
tema: 'fosc',
notificacions: true,
idioma: 'ca'
};
// 🔸 GUARDAR (Serialització a JSON string)
localStorage.setItem('config_usuari', JSON.stringify(configuracio));
// 🔸 RECUPERAR (Des-serialització a Objecte JavaScript)
const configGuardadaJSON = localStorage.getItem('config_usuari');
if (configGuardadaJSON) {
const configObjecte = JSON.parse(configGuardadaJSON);
console.log(configObjecte.tema); // Resultat: "fosc"
}
3. Iterar sobre l'emmagatzematge
Pots recórrer tots els elements utilitzant la propietat length i el mètode key().
console.log("Elements de la sessió actual:");
for (let i = 0; i < sessionStorage.length; i++) {
const clau = sessionStorage.key(i);
const valor = sessionStorage.getItem(clau);
console.log(`Clau: ${clau}, Valor: ${valor}`);
}
// Cas d'ús: Auditar o depurar el contingut emmagatzemat.
Neteja de l'emmagatzematge
S'utilitza per esborrar totes les dades emmagatzemades al domini actual.
// ATENCIÓ: Esborra TOT el localStorage
function tancarSessio() {
localStorage.clear();
sessionStorage.clear();
console.log("Tots els elements de storage eliminats.");
// Redirigir a la pàgina de login o inici
// location.href = '/login';
}
Seguretat i Límits
-
Límits de mida: La majoria dels navegadors permeten emmagatzemar entre 5 MB i 10 MB de dades per domini. Si se supera el límit, es llança una excepció.
-
Seguretat (XSS): Les dades emmagatzemades a
localStorageno són segures contra atacs Cross-Site Scripting (XSS). Qualsevol script maliciós injectat a la pàgina pot llegir-les. Per això, no s'hauria d'emmagatzemar informació altament sensible com ara contrasenyes o tokens d'autenticació de llarga durada. -
Àmbit (Scope):
localStorageisessionStorageestan restringits al mateix origen (protocol, host, i port). Els scripts d'un domini no poden accedir a l'emmagatzematge d'un altre. -
Sincronia: Les operacions són síncrones i poden bloquejar el fil d'execució principal (main thread) si es llegeixen o escriuen grans quantitats de dades. Per a dades molt grans o complexes, es recomana utilitzar IndexedDB.
IndexedDB (BBDD al navegador)
El Web Storage (com localStorage i sessionStorage) és excel·lent per a dades clau-valor petites i simples. Tanmateix, per a l'emmagatzematge de dades estructurades, grans volums d'informació, o la necessitat de consultar i indexar registres, JavaScript ofereix una opció molt més robusta: IndexedDB.
IndexedDB és una API asíncrona de JavaScript per a un emmagatzematge de dades a gran escala i altament estructurat al costat del client. És, essencialment, un sistema de base de dades NoSQL transaccional que resideix al navegador de l'usuari.
Característiques
-
Emmagatzematge asíncron: Totes les operacions són asíncrones i basades en esdeveniments (o promeses si s'utilitzen llibreries o wrappers moderns). Això significa que no bloquegen el fil d'execució principal de JavaScript (a diferència de
localStorage), la qual cosa és crucial per mantenir la fluïdesa de la interfície d'usuari. -
NoSQL (Orientada a Objectes): Emmagatzema objectes JavaScript de manera nativa, utilitzant claus per a la indexació i la recuperació. No treballa amb taules i SQL, sinó amb magatzems d'objectes (Object Stores).
-
Transaccional: Totes les modificacions es realitzen dins de transaccions. Això garanteix la integritat de les dades: si una part de l'operació falla, tota la transacció es desfà.
-
Capacitat: Els límits d'emmagatzematge solen ser molt més alts que els de
localStorage(sovint entre el 50% i el 80% de l'espai lliure del disc, amb un mínim garantit de 250 MB o més), i és l'emmagatzematge preferit per a les Progressive Web Apps (PWA).
Components
IndexedDB es basa en conceptes que reflecteixen les bases de dades tradicionals:
| Component | Concepte | Funció |
Database |
La base de dades pròpiament dita (l'equivalent al fitxer de BD). | Es defineix amb un nom (p. ex., 'LaMevaAppDB') i una versió. |
Object Store |
La unitat principal d'emmagatzematge (l'equivalent a una taula). | Conté els registres (objectes). |
Index |
Un punter a una propietat específica dels objectes. | Permet la cerca i la recuperació ràpida de dades basades en aquesta propietat (no només per la clau primària). |
Transaction |
Un embolcall que garanteix que un conjunt d'operacions s'executen completament o no s'executen en absolut. | Pot ser de lectura (readonly) o d'escriptura (readwrite). |
Mètodes i operacions
L'API IndexedDB és complexa i es basa principalment en mètodes de l'objecte IDBRequest (per gestionar les sol·licituds) i l'objecte IDBTransaction.
| Operació | Mètode / Propietat Clau | Descripció |
| Obrir BD | indexedDB.open(nom, versió) |
Inicia la connexió a la base de dades. Requereix gestionar l'esdeveniment onupgradeneeded per crear o modificar els Object Stores. |
| Afegir | objectStore.add(objecte, clau) |
Afegeix un nou objecte al magatzem. |
| Recuperar | objectStore.get(clau) |
Recupera un objecte pel seu identificador de clau. |
| Actualitzar | objectStore.put(objecte, clau) |
Actualitza un objecte existent o l'afegeix si no existeix. |
| Esborrar | objectStore.delete(clau) |
Elimina un registre pel seu identificador de clau. |
| Cursor | objectStore.openCursor() |
Permet iterar eficientment sobre grans conjunts de dades o utilitzar índexs per a la cerca avançada. |
Casos d'ús
A causa de la seva complexitat, IndexedDB s'utilitza principalment quan localStorage no és suficient:
-
Dades Offline Extenses: Emmagatzematge del cos de missatges o el contingut d'articles sencers per llegir sense connexió.
-
Cerca ràpida (Indexació): Aplicacions que necessiten cercar ràpidament registres grans (p. ex., productes en un catàleg o contactes en una llibreta).
-
Emmagatzematge d'arxius grans: Capacitat per emmagatzemar objectes binaris grans (
Blobs), cosa que permet guardar imatges o fitxers de so. -
Frameworks i llibreries: Els frameworks de frontend moderns (com Angular, React) o llibreries de dades offline (com PouchDB) solen utilitzar IndexedDB per sota de la superfície.
Exemple de recuperació de dades
L'API nativa és molt detallada, però conceptualment es veu així:
// 1. Obtenir la connexió a la base de dades
const request = indexedDB.open('CatalegProductes', 1);
request.onerror = function(event) {
console.error("Error en obrir la base de dades");
};
request.onsuccess = function(event) {
const db = event.target.result;
// 2. Iniciar una transacció de lectura
const transaccio = db.transaction(['productes'], 'readonly');
const store = transaccio.objectStore('productes');
// 3. Demanar la recuperació d'una clau
const getRequest = store.get(123);
getRequest.onsuccess = function(event) {
console.log("Producte recuperat:", event.target.result);
};
};
// Aquest procés és asíncron i no bloqueja la UI.
Exemple complet IndexedDB (catàleg llibres)
// Nom i versió de la base de dades
const DB_NAME = 'CatalegLlibresDB';
const DB_VERSION = 1;
const STORE_NAME = 'llibres'; // El nostre magatzem d'objectes (la "taula")
let db; // Variable global per a la connexió a la base de dades
// --- 1. OBRIR I CREAR LA BASE DE DADES (Si no existeix) ---
function obrirDB() {
return new Promise((resolve, reject) => {
// Sol·licita obrir la base de dades
const request = indexedDB.open(DB_NAME, DB_VERSION);
// Aquest esdeveniment es dispara si la versió de la DB no existeix o és inferior
request.onupgradeneeded = (event) => {
db = event.target.result;
console.log(`Creant o actualitzant magatzem d'objectes: ${STORE_NAME}`);
// Creem l'Object Store (la "taula")
// Definim 'isbn' com a clau primària (keyPath)
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'isbn' });
// També podríem crear un índex per a cerques per 'autor', per exemple
// store.createIndex('per_autor', 'autor', { unique: false });
}
};
request.onsuccess = (event) => {
db = event.target.result;
console.log("Base de dades oberta amb èxit!");
resolve();
};
request.onerror = (event) => {
console.error("Error en obrir la base de dades:", event.target.errorCode);
reject(new Error("No es pot obrir la DB"));
};
});
}
// --- 2. AFegir ELEMENTS (CRUD: Create) ---
function afegirLlibre(llibre) {
if (!db) {
console.error("La base de dades no està connectada.");
return;
}
// 1. Iniciar una transacció de lectura/escriptura
const transaccio = db.transaction([STORE_NAME], 'readwrite');
const store = transaccio.objectStore(STORE_NAME);
// 2. Afegir l'objecte
const request = store.add(llibre);
request.onsuccess = () => {
console.log(`Llibre afegit amb ISBN ${llibre.isbn}`);
};
request.onerror = (event) => {
// Això passa sovint si ja existeix un element amb la mateixa clau (ISBN)
console.error("Error en afegir el llibre:", event.target.error);
};
}
// --- 3. CONSULTAR UN ELEMENT PER CLAU (CRUD: Read) ---
function consultarLlibre(isbn) {
if (!db) {
console.error("La base de dades no està connectada.");
return;
}
// 1. Iniciar una transacció de només lectura
const transaccio = db.transaction([STORE_NAME], 'readonly');
const store = transaccio.objectStore(STORE_NAME);
// 2. Recuperar l'element per la seva clau primària (ISBN)
const request = store.get(isbn);
request.onsuccess = (event) => {
const llibre = event.target.result;
if (llibre) {
console.log(`\n--- Llibre trobat (ISBN ${isbn}) ---`);
console.log(llibre);
} else {
console.log(`\nNo s'ha trobat cap llibre amb ISBN ${isbn}`);
}
};
request.onerror = (event) => {
console.error("Error en consultar el llibre:", event.target.error);
};
}
// --- 4. ELIMINAR UN ELEMENT PER CLAU (CRUD: Delete) ---
function eliminarLlibre(isbn) {
if (!db) {
console.error("La base de dades no està connectada.");
return;
}
// 1. Iniciar una transacció de lectura/escriptura
const transaccio = db.transaction([STORE_NAME], 'readwrite');
const store = transaccio.objectStore(STORE_NAME);
// 2. Eliminar l'element per la seva clau primària (ISBN)
const request = store.delete(isbn);
request.onsuccess = () => {
console.log(`\nLlibre amb ISBN ${isbn} eliminat correctament.`);
};
request.onerror = (event) => {
console.error("Error en eliminar el llibre:", event.target.error);
};
}
// --- FLUX D'EXECUCIÓ (Async/Await per simplificar) ---
const llibre1 = { isbn: '978-8426102604', titol: 'El petit príncep', autor: 'Antoine de Saint-Exupéry', any: 1943 };
const llibre2 = { isbn: '978-8429749103', titol: 'Tirant lo Blanc', autor: 'Joanot Martorell', any: 1490 };
// Esperem a que la base de dades estigui oberta abans de fer operacions
obrirDB()
.then(() => {
// Afegeix el llibre 1
afegirLlibre(llibre1);
// Afegeix el llibre 2
afegirLlibre(llibre2);
// Consulta el llibre 1 després d'un petit retard (per assegurar-nos que s'ha escrit)
setTimeout(() => consultarLlibre('978-8426102604'), 500);
// Intentem consultar un ISBN que no existeix
setTimeout(() => consultarLlibre('111-2223334445'), 1000);
// Elimina el llibre 2
setTimeout(() => eliminarLlibre('978-8429749103'), 1500);
// Comprovem que s'ha eliminat
setTimeout(() => consultarLlibre('978-8429749103'), 2000);
})
.catch((error) => {
console.error("No es pot continuar. La DB no s'ha pogut inicialitzar.");
});