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:
- Session Storage: Dane przechowywane w
sessionStorage
są dostępne tylko podczas trwania bieżącej sesji przeglądarki. Sesja kończy się wraz z zamknięciem karty lub okna przeglądarki. Otwarcie tej samej strony w nowej karcie lub oknie rozpoczyna nową, oddzielną sesję z własnymsessionStorage
. - Local Storage: Dane przechowywane w
localStorage
są trwałe. Pozostają dostępne nawet po zamknięciu i ponownym otwarciu przeglądarki. Dane te nie mają daty wygaśnięcia i pozostają, dopóki nie zostaną jawnie usunięte przez kod JavaScript lub użytkownika (np. przez wyczyszczenie danych przeglądarki).
Inne różnice:
- Zakres: Oba typy storage są ograniczone do konkretnego źródła (origin), czyli kombinacji protokołu, domeny i portu. Strona z
http://example.com
nie ma dostępu do storage stronyhttps://example.com
anihttp://sub.example.com
. - Rozmiar: Dostępny rozmiar jest znacznie większy niż dla ciasteczek, zazwyczaj wynosi 5-10 MB na źródło (dokładny limit zależy od przeglądarki).
- API: Oba obiekty (
localStorage
isessionStorage
) mają identyczne API (metody i właściwości).
API Web Storage
Obiekty localStorage
i sessionStorage
są dostępne globalnie jako właściwości obiektu window
. Mają one następujące metody:
setItem(key, value)
: Dodaje nową parę klucz-wartość lub aktualizuje wartość dla istniejącego klucza. Oba argumenty muszą być stringami.getItem(key)
: Pobiera wartość powiązaną z podanym kluczem. Zwraca string lubnull
, jeśli klucz nie istnieje.removeItem(key)
: Usuwa parę klucz-wartość dla podanego klucza.clear()
: Usuwa wszystkie pary klucz-wartość przechowywane w danym storage (dla danego źródła).key(index)
: Zwraca nazwę klucza na podanej pozycji (indeksie). Przydatne do iteracji po wszystkich kluczach.length
: Właściwość (nie metoda) zwracająca liczbę przechowywanych par klucz-wartość.
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
- Tylko stringi: Jak wspomniano, można przechowywać tylko dane tekstowe.
- Rozmiar: Mimo że limit jest większy niż dla ciasteczek, nadal jest ograniczony (zwykle 5-10MB). Przekroczenie limitu spowoduje błąd (
QuotaExceededError
). - Synchroniczność: Operacje na Web Storage (
setItem
,getItem
itp.) są synchroniczne. Oznacza to, że mogą potencjalnie zablokować główny wątek przeglądarki, jeśli operacje są bardzo częste lub dane są duże. Dla bardziej wymagających zastosowań rozważ IndexedDB (asynchroniczne API). - Bezpieczeństwo: Dane w Web Storage nie są szyfrowane i są podatne na ataki typu Cross-Site Scripting (XSS). Jeśli złośliwy skrypt zostanie wstrzyknięty na stronę, może on odczytać lub zmodyfikować dane w
localStorage
isessionStorage
. Nigdy nie przechowuj wrażliwych danych (haseł, tokenów sesji o długim czasie życia, danych osobowych) w Web Storage. Do bezpiecznego przechowywania tokenów lepiej nadają się ciasteczka z flagamiHttpOnly
iSecure
.
Zadanie praktyczne
Stwórz prosty formularz HTML z jednym polem tekstowym (<input type="text" id="userInput">
) i przyciskiem "Zapisz".
Napisz kod JavaScript, który:
- Przy ładowaniu strony sprawdzi, czy w
localStorage
istnieje zapisana wartość pod kluczem "ostatniWpis". Jeśli tak, ustawi tę wartość w polu tekstowym. - 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.