五种浏览器存储机制概览
现代浏览器提供了 5 种主要存储机制,每种都有不同的设计目标和适用场景:
| 存储机制 |
数据类型 |
容量上限 |
持久性 |
线程访问 |
| localStorage |
字符串键值对 |
~5-10 MB |
永久 |
仅主线程 |
| sessionStorage |
字符串键值对 |
~5-10 MB |
标签页关闭 |
仅主线程 |
| IndexedDB |
结构化数据 + Blob |
数百 MB~无上限 |
永久 |
主线程 + 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 |
| 生命周期 |
永久存储 |
标签页关闭即清除 |
| 作用域 |
同源所有标签页共享 |
仅当前标签页 |
| 典型用途 |
用户偏好、Token |
表单临时数据、页面状态 |
容量检测
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 配额已满');
} 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: '张三', email: 'zhang@example.com' });
store.get(1).onsuccess = (e) => {
console.log(e.target.result);
};
};
高级特性
| 特性 |
说明 |
| 事务 |
保证原子性,读写分离 |
| 索引 |
支持非主键字段的高效查询 |
| 游标 |
逐条遍历大量数据,避免一次性加载 |
| Blob 存储 |
可直接存储 File 和 Blob 对象 |
| Worker 访问 |
Service Worker 和 Web Worker 中可用 |
使用 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: '张三' });
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 |
仅从网络获取 |
非 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 与传统文件 API 对比
| 特性 |
OPFS |
File System Access API |
| 用户授权 |
不需要 |
需要用户选择目录 |
| 可见性 |
浏览器内部虚拟文件系统 |
操作系统真实文件系统 |
| Worker 同步 |
支持 |
不支持 |
| 持久性 |
永久 |
取决于用户授权 |
| 适用场景 |
高性能临时文件处理 |
文件编辑器、导出 |
存储配额与持久化
查询可用配额
const estimate = await navigator.storage.estimate();
console.log(`已用: ${(estimate.usage / 1024 / 1024).toFixed(2)} MB`);
console.log(`配额: ${(estimate.quota / 1024 / 1024).toFixed(2)} MB`);
请求持久化
const persisted = await navigator.storage.persist();
if (persisted) {
console.log('存储已持久化,不会自动清除');
} else {
console.log('存储可能在压力下被浏览器清除');
}
浏览器在存储压力下可能自动清除非持久化存储。调用 navigator.storage.persist() 可请求持久化权限。
选型决策指南
需要存储什么?
├── 简单键值对(< 5MB)
│ ├── 需要跨标签页共享 → localStorage
│ └── 仅当前标签页 → sessionStorage
├── 结构化数据 / 大量记录
│ └── IndexedDB
├── HTTP 请求/响应缓存
│ └── Cache API
└── 大文件 / 二进制数据 / 高性能读写
└── OPFS
实际应用示例
| 场景 |
推荐存储 |
原因 |
| 用户主题偏好 |
localStorage |
简单键值,跨页面共享 |
| 表单草稿 |
sessionStorage |
临时性,标签页隔离 |
| 离线工具数据 |
IndexedDB |
结构化,支持索引查询 |
| 静态资源缓存 |
Cache API |
专为 Request/Response 设计 |
| 视频处理临时文件 |
OPFS |
Worker 同步读写,高性能 |
安全注意事项
- XSS 风险:localStorage 可被 XSS 读取,不要存储敏感 Token
- HttpOnly Cookie:认证 Token 应使用 HttpOnly + Secure Cookie
- 存储加密:敏感数据存储前用 JWT 或 Hash 处理
- Same-Origin 隔离:所有存储遵循同源策略,跨域不可访问
总结
浏览器存储已从单一的 localStorage 发展为覆盖键值、结构化数据、HTTP 缓存和文件系统的完整体系。选型核心是匹配数据特征:简单配置用 localStorage,结构化数据用 IndexedDB,HTTP 缓存用 Cache API,高性能文件处理用 OPFS。
使用 Cookie 解析工具 检查 Cookie 配置,使用 JWT 工具 验证 Token 安全性,使用 Hash 工具 对敏感数据进行哈希处理。