Browser Storage Compared: localStorage, IndexedDB, Cache API, and OPFS
技术架构(Updated Jun 8, 2026)
Overview of Five Browser Storage Mechanisms
Modern browsers offer 5 primary storage mechanisms, each designed for different goals and use cases:
| Storage | Data Type | Capacity Limit | Persistence | Thread Access |
|---|---|---|---|---|
| localStorage | String key-value | ~5-10 MB | Permanent | Main thread only |
| sessionStorage | String key-value | ~5-10 MB | Tab close | Main thread only |
| IndexedDB | Structured data + Blob | Hundreds of MB~unlimited | Permanent | Main thread + Worker |
| Cache API | Request/Response pairs | Shared with IndexedDB quota | Permanent | Main thread + Worker |
| OPFS | File system | Shared with IndexedDB quota | Permanent | Worker only (sync) |
localStorage and sessionStorage
Basic Usage
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));
}
Key Differences
| Feature | localStorage | sessionStorage |
|---|---|---|
| Lifetime | Permanent | Cleared on tab close |
| Scope | Shared across same-origin tabs | Current tab only |
| Typical use | User preferences, tokens | Form drafts, page state |
Capacity Detection
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);
}
}
Caveats
- Synchronous blocking: Read/write blocks the main thread; large data hurts performance
- Strings only: Objects need
JSON.stringify/JSON.parse - Storage event: Fires
storageevent in other same-origin tabs on modification
IndexedDB
Basic Usage
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);
};
};
Advanced Features
| Feature | Description |
|---|---|
| Transactions | Guarantees atomicity, read/write separation |
| Indexes | Efficient queries on non-key fields |
| Cursors | Iterate large datasets without loading all at once |
| Blob storage | Store File and Blob objects directly |
| Worker access | Available in Service Workers and Web Workers |
Simplified with the idb Library
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
Basic Usage
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');
With 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;
});
})
);
});
Caching Strategies Compared
| Strategy | Description | Use Case |
|---|---|---|
| Cache First | Cache first, network on miss | Static assets, fonts |
| Network First | Network first, cache fallback | API data, frequently updated content |
| Stale While Revalidate | Return cache, update in background | Non-critical APIs, images |
| Cache Only | Cache only | Offline pages, precached resources |
| Network Only | Network only | Non-GET requests, real-time data |
OPFS (Origin Private File System)
Basic Usage
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();
Synchronous Access in Workers
// In a Worker — synchronous OPFS (extremely fast)
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
| Feature | OPFS | File System Access API |
|---|---|---|
| User permission | Not required | User must pick directory |
| Visibility | Browser-internal virtual FS | Real OS file system |
| Worker sync | Supported | Not supported |
| Persistence | Permanent | Depends on user grant |
| Use case | High-performance temp file processing | File editors, export |
Storage Quota and Persistence
Query Available Quota
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`);
Request Persistence
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');
}
Browsers may auto-evict non-persistent storage under pressure. Call
navigator.storage.persist()to request persistence.
Decision Guide
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
Real-World Examples
| Scenario | Recommended Storage | Reason |
|---|---|---|
| User theme preference | localStorage | Simple key-value, cross-page |
| Form draft | sessionStorage | Temporary, tab-scoped |
| Offline tool data | IndexedDB | Structured, indexed queries |
| Static asset cache | Cache API | Designed for Request/Response |
| Video processing temp files | OPFS | Worker sync I/O, high performance |
Security Considerations
- XSS risk: localStorage is readable by XSS — never store sensitive tokens
- HttpOnly cookies: Auth tokens should use HttpOnly + Secure cookies
- Storage encryption: Encrypt sensitive data with JWT or Hash before storing
- Same-origin isolation: All storage follows same-origin policy; cross-origin access is blocked
Summary
Browser storage has evolved from a single localStorage to a complete system covering key-value pairs, structured data, HTTP caching, and file systems. The key to selection is matching data characteristics: simple config → localStorage, structured data → IndexedDB, HTTP caching → Cache API, high-performance file I/O → OPFS.
Use the Cookie Parser to inspect cookie configuration, the JWT Tool to verify token security, and the Hash Tool to hash sensitive data.
Try these browser-local tools — no sign-up required →
#localStorage#sessionStorage#IndexedDB#Cache API#存储对比