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.

JavaScript
// 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.

JavaScript
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().

JavaScript
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.

JavaScript
// 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 localStorage no 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): localStorage i sessionStorage estan 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

  1. 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.

  2. 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).

  3. 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à.

  4. 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:

  1. Dades Offline Extenses: Emmagatzematge del cos de missatges o el contingut d'articles sencers per llegir sense connexió.

  2. Cerca ràpida (Indexació): Aplicacions que necessiten cercar ràpidament registres grans (p. ex., productes en un catàleg o contactes en una llibreta).

  3. Emmagatzematge d'arxius grans: Capacitat per emmagatzemar objectes binaris grans (Blobs), cosa que permet guardar imatges o fitxers de so.

  4. 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í:

JavaScript
// 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.");
    });

 


Revisió núm. 1
Admin l’ha creat 2025-09-30 12:45:01 UTC
Admin l’ha actualitzat 2025-09-30 12:58:44 UTC