Обзор пяти механизмов хранения браузера
Современные браузеры предлагают 5 основных механизмов хранения, каждый из которых предназначен для разных целей и сценариев использования:
| Хранилище |
Тип данных |
Лимит ёмкости |
Персистентность |
Доступ из потоков |
| localStorage |
Строковые ключ-значение |
~5-10 МБ |
Постоянное |
Только главный поток |
| sessionStorage |
Строковые ключ-значение |
~5-10 МБ |
При закрытии вкладки |
Только главный поток |
| IndexedDB |
Структурированные данные + Blob |
Сотни МБ~без ограничений |
Постоянное |
Главный поток + Worker |
| Cache API |
Пары Request/Response |
Общая квота с IndexedDB |
Постоянное |
Главный поток + Worker |
| OPFS |
Файловая система |
Общая квота с IndexedDB |
Постоянное |
Только Worker (синхронно) |
localStorage и sessionStorage
Базовое использование
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');
localStorage.removeItem('theme');
localStorage.clear();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
Ключевые различия
| Свойство |
localStorage |
sessionStorage |
| Время жизни |
Постоянное |
Очищается при закрытии вкладки |
| Область видимости |
Общее для вкладок одного источника |
Только текущая вкладка |
| Типичное использование |
Настройки пользователя, токены |
Черновики форм, состояние страницы |
Определение ёмкости
function testLocalStorageQuota() {
const testKey = '__quota_test__';
let data = '';
try {
for (let i = 0; i < 1024; i++) data += 'a'.repeat(1024);
for (let mb = 1; mb < 20; mb++) {
localStorage.setItem(testKey, data.repeat(mb));
}
} catch (e) {
console.log('localStorage quota exceeded');
} finally {
localStorage.removeItem(testKey);
}
}
Предостережения
- Синхронная блокировка: Чтение/запись блокирует главный поток; большие данные ухудшают производительность
- Только строки: Объектам требуется
JSON.stringify/JSON.parse
- Событие storage: Вызывает событие
storage в других вкладках того же источника при изменении
IndexedDB
Базовое использование
const openRequest = indexedDB.open('myDatabase', 1);
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('users', { keyPath: 'id' });
store.createIndex('name', 'name', { unique: false });
};
openRequest.onsuccess = (event) => {
const db = event.target.result;
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice', email: 'alice@example.com' });
store.get(1).onsuccess = (e) => {
console.log(e.target.result);
};
};
Расширенные возможности
| Возможность |
Описание |
| Транзакции |
Гарантируют атомарность, разделение чтения/записи |
| Индексы |
Эффективные запросы по неключевым полям |
| Курсоры |
Итерация больших наборов данных без загрузки всех сразу |
| Хранение Blob |
Прямое хранение объектов File и Blob |
| Доступ из Worker |
Доступно в Service Workers и Web Workers |
Упрощение с библиотекой idb
import { openDB } from 'idb';
const db = await openDB('myDatabase', 1, {
upgrade(db) {
db.createObjectStore('users', { keyPath: 'id' });
}
});
await db.put('users', { id: 1, name: 'Alice' });
const user = await db.get('users', 1);
const allUsers = await db.getAll('users');
await db.delete('users', 1);
Cache API
Базовое использование
const cache = await caches.open('v1');
await cache.put('/api/data', new Response(JSON.stringify({ foo: 'bar' })));
const response = await cache.match('/api/data');
const data = await response.json();
await cache.delete('/api/data');
С Service Worker
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('v1').then((cache) => {
cache.put(event.request, clone);
});
return response;
});
})
);
});
Сравнение стратегий кэширования
| Стратегия |
Описание |
Сценарий использования |
| Cache First |
Сначала кэш, сеть при промахе |
Статические ресурсы, шрифты |
| Network First |
Сначала сеть, кэш как резерв |
Данные API, часто обновляемый контент |
| Stale While Revalidate |
Вернуть кэш, обновить в фоновом режиме |
Некритичные API, изображения |
| Cache Only |
Только кэш |
Офлайн-страницы, предварительно кэшированные ресурсы |
| Network Only |
Только сеть |
Non-GET запросы, данные в реальном времени |
OPFS (Origin Private File System)
Базовое использование
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
const writable = await fileHandle.createWritable();
await writable.write(new Uint8Array([1, 2, 3, 4]));
await writable.close();
const file = await fileHandle.getFile();
const buffer = await file.arrayBuffer();
Синхронный доступ в Worker
// В Worker — синхронный OPFS (крайне быстрый)
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('large.bin', { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();
const buffer = new ArrayBuffer(4);
accessHandle.write(buffer, { at: 0 });
accessHandle.flush();
accessHandle.close();
OPFS vs File System Access API
| Свойство |
OPFS |
File System Access API |
| Разрешение пользователя |
Не требуется |
Пользователь должен выбрать каталог |
| Видимость |
Внутренняя виртуальная ФС браузера |
Реальная ФС операционной системы |
| Синхронность в Worker |
Поддерживается |
Не поддерживается |
| Персистентность |
Постоянное |
Зависит от разрешения пользователя |
| Сценарий использования |
Высокопроизводительная обработка временных файлов |
Файловые редакторы, экспорт |
Квота хранилища и персистентность
Запрос доступной квоты
const estimate = await navigator.storage.estimate();
console.log(`Used: ${(estimate.usage / 1024 / 1024).toFixed(2)} MB`);
console.log(`Quota: ${(estimate.quota / 1024 / 1024).toFixed(2)} MB`);
Запрос персистентности
const persisted = await navigator.storage.persist();
if (persisted) {
console.log('Storage persisted, will not be auto-evicted');
} else {
console.log('Storage may be cleared under pressure');
}
Браузеры могут автоматически удалять неперсистентное хранилище при нехватке ресурсов. Вызовите navigator.storage.persist(), чтобы запросить персистентность.
Руководство по выбору
What do you need to store?
├── Simple key-value (< 5MB)
│ ├── Cross-tab sharing → localStorage
│ └── Current tab only → sessionStorage
├── Structured data / many records
│ └── IndexedDB
├── HTTP request/response caching
│ └── Cache API
└── Large files / binary / high-performance I/O
└── OPFS
Практические примеры
| Сценарий |
Рекомендуемое хранилище |
Причина |
| Настройка темы пользователя |
localStorage |
Простой ключ-значение, между страницами |
| Черновик формы |
sessionStorage |
Временное, в пределах вкладки |
| Данные офлайн-инструмента |
IndexedDB |
Структурированные, индексированные запросы |
| Кэш статических ресурсов |
Cache API |
Разработано для Request/Response |
| Временные файлы обработки видео |
OPFS |
Синхронный ввод-вывод в Worker, высокая производительность |
Вопросы безопасности
- Риск XSS: localStorage доступен для чтения через XSS — никогда не храните конфиденциальные токены
- HttpOnly-куки: Токены аутентификации должны использовать HttpOnly + Secure куки
- Шифрование хранилища: Шифруйте конфиденциальные данные с помощью JWT или Hash перед сохранением
- Изоляция одного источника: Все хранилища следуют политике одного источника; межисточниковый доступ заблокирован
Заключение
Хранилище браузера эволюционировало от единственного localStorage до полноценной системы, охватывающей пары ключ-значение, структурированные данные, HTTP-кэширование и файловые системы. Ключ к выбору — соответствие характеристикам данных: простая конфигурация → localStorage, структурированные данные → IndexedDB, HTTP-кэширование → Cache API, высокопроизводительный файловый ввод-вывод → OPFS.
Используйте Парсер куки для проверки конфигурации куки, Инструмент JWT для проверки безопасности токенов и Инструмент Hash для хеширования конфиденциальных данных.