Lekcja 20: Web Storage (Local Storage i Session Storage)

Aplikacje internetowe często potrzebują przechowywać pewne dane po stronie klienta (w przeglądarce użytkownika), aby zachować stan między sesjami lub odświeżeniami strony. Historycznie używano do tego ciasteczek (cookies), ale mają one swoje ograniczenia (mały rozmiar, wysyłane z każdym żądaniem HTTP). Nowocześniejszym i bardziej elastycznym rozwiązaniem jest Web Storage API, które dostarcza dwa mechanizmy: Local Storage i Session Storage.

Oba te mechanizmy pozwalają na przechowywanie danych w postaci par klucz-wartość (oba muszą być stringami) bezpośrednio w przeglądarce użytkownika.

Różnice między Local Storage a Session Storage

Główna różnica polega na czasie życia przechowywanych danych:

Inne różnice:

API Web Storage

Obiekty localStorage i sessionStorage są dostępne globalnie jako właściwości obiektu window. Mają one następujące metody:

Można również uzyskiwać dostęp do danych jak do właściwości obiektu (np. localStorage.nazwaKlucza = "wartość" lub let wartosc = localStorage.nazwaKlucza), ale używanie metod setItem/getItem jest zalecane, ponieważ jest bardziej jawne i unika potencjalnych konfliktów z wbudowanymi właściwościami obiektu Storage.

// --- Przykład użycia localStorage --- 

// Sprawdzenie, czy przeglądarka wspiera localStorage
if (typeof(Storage) !== "undefined") {
    console.log("Web Storage jest wspierany.");

    // Zapisywanie danych
    localStorage.setItem("ulubionyKolor", "niebieski");
    localStorage.setItem("nazwaUzytkownika", "JanKowalski");
    localStorage.licznikOdwiedzin = 1; // Można też tak, ale setItem jest lepsze

    console.log("Zapisano dane w localStorage.");

    // Odczytywanie danych
    let kolor = localStorage.getItem("ulubionyKolor");
    let user = localStorage.getItem("nazwaUzytkownika");
    let nieistniejacy = localStorage.getItem("innyKlucz");

    console.log("Ulubiony kolor:", kolor); // "niebieski"
    console.log("Nazwa użytkownika:", user); // "JanKowalski"
    console.log("Licznik odwiedzin:", localStorage.licznikOdwiedzin); // "1" (zwraca string!)
    console.log("Nieistniejący klucz:", nieistniejacy); // null

    // Aktualizacja danych
    localStorage.setItem("ulubionyKolor", "zielony");
    console.log("Zaktualizowany kolor:", localStorage.getItem("ulubionyKolor")); // "zielony"

    // Usuwanie konkretnego elementu
    localStorage.removeItem("nazwaUzytkownika");
    console.log("Nazwa użytkownika po usunięciu:", localStorage.getItem("nazwaUzytkownika")); // null

    // Sprawdzenie liczby elementów
    console.log("Liczba elementów w localStorage:", localStorage.length);

    // Iteracja po kluczach
    console.log("Klucze w localStorage:");
    for (let i = 0; i < localStorage.length; i++) {
        let key = localStorage.key(i);
        let value = localStorage.getItem(key);
        console.log(`  ${key}: ${value}`);
    }

    // Czyszczenie całego localStorage (używaj ostrożnie!)
    // localStorage.clear();
    // console.log("localStorage wyczyszczony. Liczba elementów:", localStorage.length); // 0

} else {
    console.log("Przeglądarka nie wspiera Web Storage.");
}

// --- Przykład użycia sessionStorage --- 
// API jest identyczne, wystarczy zamienić localStorage na sessionStorage

sessionStorage.setItem("idSesji", "xyz123abc");
let sessionId = sessionStorage.getItem("idSesji");
console.log("ID sesji (sessionStorage):", sessionId);
// Po zamknięciu karty/okna i ponownym otwarciu, ta wartość zniknie.

Przechowywanie obiektów i tablic

Web Storage może przechowywać tylko stringi. Jeśli chcemy zapisać bardziej złożone struktury danych, takie jak obiekty JavaScript czy tablice, musimy je najpierw przekonwertować na string w formacie JSON za pomocą JSON.stringify(), a podczas odczytu sparsować z powrotem do obiektu/tablicy za pomocą JSON.parse().

const ustawieniaUzytkownika = {
    motyw: "ciemny",
    powiadomienia: true,
    ulubione: ["sport", "muzyka"]
};

// Zapis obiektu do localStorage
try {
    localStorage.setItem("ustawienia", JSON.stringify(ustawieniaUzytkownika));
    console.log("Zapisano obiekt ustawień.");
} catch (error) {
    console.error("Błąd podczas zapisywania obiektu do localStorage:", error);
}

// Odczyt obiektu z localStorage
try {
    const zapisaneUstawieniaString = localStorage.getItem("ustawienia");
    if (zapisaneUstawieniaString) {
        const odczytaneUstawienia = JSON.parse(zapisaneUstawieniaString);
        console.log("Odczytane ustawienia (obiekt):", odczytaneUstawienia);
        console.log("Motyw:", odczytaneUstawienia.motyw); // "ciemny"
        console.log("Ulubione:", odczytaneUstawienia.ulubione); // ["sport", "muzyka"]
    } else {
        console.log("Brak zapisanych ustawień.");
    }
} catch (error) {
    // Błąd może wystąpić, jeśli zapisany string nie jest poprawnym JSON-em
    console.error("Błąd podczas parsowania ustawień z localStorage:", error);
}

Ograniczenia i bezpieczeństwo

Zadanie praktyczne

Stwórz prosty formularz HTML z jednym polem tekstowym (<input type="text" id="userInput">) i przyciskiem "Zapisz".

Napisz kod JavaScript, który:

  1. Przy ładowaniu strony sprawdzi, czy w localStorage istnieje zapisana wartość pod kluczem "ostatniWpis". Jeśli tak, ustawi tę wartość w polu tekstowym.
  2. Po kliknięciu przycisku "Zapisz" pobierze aktualną wartość z pola tekstowego i zapisze ją w localStorage pod kluczem "ostatniWpis".

Dzięki temu wpisana wartość będzie pamiętana nawet po zamknięciu i ponownym otwarciu przeglądarki.

Pokaż rozwiązanie

HTML (fragment):

<label for="userInput">Wpisz coś:</label>
<input type="text" id="userInput">
<button id="saveButton">Zapisz</button>

JavaScript:

const inputElement = document.getElementById("userInput");
const saveButton = document.getElementById("saveButton");
const storageKey = "ostatniWpis";

// Funkcja do ładowania danych przy starcie
function zaladujZapisanyWpis() {
    try {
        const zapisanyWpis = localStorage.getItem(storageKey);
        if (zapisanyWpis !== null) {
            inputElement.value = zapisanyWpis;
            console.log("Załadowano zapisany wpis z localStorage.");
        }
    } catch (error) {
        console.error("Błąd odczytu z localStorage:", error);
    }
}

// Funkcja do zapisywania danych po kliknięciu
function zapiszWpis() {
    try {
        const aktualnyWpis = inputElement.value;
        localStorage.setItem(storageKey, aktualnyWpis);
        console.log(`Zapisano "${aktualnyWpis}" w localStorage.`);
    } catch (error) {
        console.error("Błąd zapisu do localStorage:", error);
    }
}

// Nasłuchiwanie na zdarzenie kliknięcia przycisku
saveButton.addEventListener("click", zapiszWpis);

// Ładowanie danych przy starcie skryptu (po załadowaniu DOM)
document.addEventListener("DOMContentLoaded", zaladujZapisanyWpis);

Zadanie do samodzielnego wykonania

Rozbuduj poprzednie zadanie. Dodaj drugi przycisk "Wyczyść". Po kliknięciu tego przycisku, usuń wpis "ostatniWpis" z localStorage i wyczyść zawartość pola tekstowego.

FAQ - Web Storage

Kiedy używać Local Storage, a kiedy Session Storage?

Używaj localStorage, gdy chcesz, aby dane były dostępne między sesjami, nawet po zamknięciu przeglądarki (np. preferencje użytkownika, zawartość koszyka offline). Używaj sessionStorage dla danych tymczasowych, które mają być dostępne tylko w ramach jednej sesji (np. stan formularza wieloetapowego, tymczasowe dane użytkownika po zalogowaniu).

Czy Web Storage działa w trybie Incognito/Prywatnym?

Tak, ale zachowuje się jak sessionStorage. Dane zapisane w localStorage lub sessionStorage w trybie prywatnym są usuwane po zamknięciu ostatniego okna/karty w tym trybie. Każda sesja prywatna ma swój własny, izolowany storage.

Jak mogę zobaczyć, co jest zapisane w Web Storage?

Większość przeglądarek oferuje narzędzia deweloperskie (dostępne zazwyczaj pod klawiszem F12), które mają zakładkę "Application" (lub "Storage"). W tej zakładce można przeglądać, edytować i usuwać dane zapisane w Local Storage i Session Storage dla bieżącej strony.

Czy istnieje zdarzenie informujące o zmianie w Web Storage?

Tak, istnieje zdarzenie storage. Jest ono wywoływane w oknie (window), gdy dane w localStorage lub sessionStorage zostaną zmienione (dodane, zaktualizowane, usunięte) w innej karcie lub oknie dla tego samego źródła. Nie jest wywoływane w tej samej karcie, która dokonała zmiany.

Jakie są alternatywy dla Web Storage?

Główne alternatywy to: Ciasteczka (Cookies) - małe, wysyłane z żądaniami, dobre do tokenów sesji; IndexedDB - asynchroniczne API do przechowywania dużych ilości strukturalnych danych po stronie klienta (bardziej skomplikowane); Cache API (część Service Workers) - do przechowywania odpowiedzi sieciowych offline.

Co się stanie, gdy spróbuję zapisać więcej danych niż pozwala limit?

Próba zapisu danych (za pomocą setItem), która przekroczyłaby dostępny limit (quota), spowoduje rzucenie błędu DOMException, często określanego jako QuotaExceededError. Dlatego warto opakowywać operacje zapisu w bloki try...catch, zwłaszcza przy potencjalnie dużych danych.