localStorage / sessionStorage / cookies / IndexedDB
Браузер предоставляет несколько механизмов хранения с разными областями видимости, лимитами и семантикой; выбор неправильного хранилища — например, JWT в localStorage вместо httpOnly-cookie — классический security-вопрос на senior-собеседованиях.
// localStorage / sessionStorage: синхронный key-value (~5 МБ), только строкиlocalStorage.setItem('theme', 'dark');const theme = localStorage.getItem('theme'); // 'dark' или nulllocalStorage.removeItem('theme');localStorage.clear();
// sessionStorage: то же API, но живёт только в рамках одной вкладки/сессии// Дублирование вкладки копирует sessionStorage, но копии независимыsessionStorage.setItem('draft', JSON.stringify({ text: 'hello', ts: Date.now() }));const draft = JSON.parse(sessionStorage.getItem('draft') ?? 'null');
// storage event: межвкладочная коммуникация (только localStorage, не sessionStorage)window.addEventListener('storage', e => { // e.key | e.oldValue | e.newValue | e.storageArea | e.url if (e.key === 'logout') location.reload();});
// Оба API синхронны → блокируют main thread при больших данных.// Для > 1 МБ структурированных данных: IndexedDB.// Cookies: атрибуты безопасности имеют значение// Установка через JS (document.cookie — одна пара за раз)document.cookie = 'pref=dark; Max-Age=2592000; Path=/; SameSite=Lax';// httpOnly и Secure нельзя выставить из JS — только сервер!
// Правильная установка session-cookie на сервере (Node.js / Express):// res.cookie('sid', sessionId, {// httpOnly: true, // недоступен из JS → защита от XSS-кражи// secure: true, // только HTTPS// sameSite: 'strict', // защита от CSRF// maxAge: 3_600_000, // 1 час в мс// path: '/',// });
// Чтение всех доступных cookies (httpOnly — не видны)const cookies = Object.fromEntries( document.cookie.split('; ').filter(Boolean).map(c => { const idx = c.indexOf('='); return [c.slice(0, idx), decodeURIComponent(c.slice(idx + 1))]; }));
// SameSite=Strict: cookie не отправляется при любом cross-site запросе// SameSite=Lax : только top-level GET-навигация (Chrome 80+ default)// SameSite=None : всегда (требует Secure=true)// IndexedDB: асинхронная объектная БД, гигабайты, structured cloneconst request = indexedDB.open('appDB', 1);
request.onupgradeneeded = e => { const db = e.target.result; const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true }); store.createIndex('by_date', 'createdAt', { unique: false });};
request.onsuccess = e => { const db = e.target.result; const tx = db.transaction('notes', 'readwrite'); const st = tx.objectStore('notes'); st.add({ text: 'hello', createdAt: Date.now() }); tx.oncomplete = () => console.log('saved'); tx.onerror = () => console.error(tx.error);};
// Современная обёртка — библиотека idb (Jake Archibald, ~1 КБ gzip):// import { openDB } from 'idb';// const db = await openDB('appDB', 1, {// upgrade(db) { db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true }); }// });// const id = await db.add('notes', { text: 'hi', createdAt: Date.now() });// const all = await db.getAll('notes');
// Cache API (Service Worker): хранение Response-объектов (ресурсы, HTML, JSON)// Не путать с HTTP-кэшем; управляется вручную через caches.open('v1')Итог: Правило выбора: чувствительные токены → httpOnly-cookie; UI-настройки → localStorage; per-tab draft → sessionStorage; большие структурированные данные → IndexedDB; офлайн-ресурсы → Cache API.