浏览器存储
前言
浏览器存储是 Web 开发中不可或缺的能力。随着 Web 应用从简单的文档页面演进为复杂的单页应用(SPA)和渐进式 Web 应用(PWA),客户端存储的需求日益增长。浏览器提供了多种存储机制,每种机制在设计目标、容量限制、生命周期和作用域等方面各有差异。本文将系统性地介绍五种主要的浏览器存储方案,帮助开发者理解其原理并做出合理的技术选型。
一、Cookie
1.1 概述
Cookie 是最早的浏览器存储机制,由 Netscape 公司在 1994 年提出,最初用于解决 HTTP 无状态协议下的会话管理问题。Cookie 本质上是一段小型的文本数据,由服务器通过 HTTP 响应头 Set-Cookie 设置,浏览器自动在后续请求中通过 Cookie 请求头带回。
1.2 设置与读取
服务器端设置
服务器通过在 HTTP 响应中添加 Set-Cookie 头来设置 Cookie:
Set-Cookie: sessionId=abc123; Path=/; HttpOnly客户端操作
在 JavaScript 中通过 document.cookie 接口读写 Cookie。需要注意的是,document.cookie 是一个奇葩的 API——读取时返回所有非 HttpOnly 的 Cookie(以分号分隔的键值对字符串),写入时则只能设置单个 Cookie:
// 读取所有 Cookie
console.log(document.cookie);
// 设置一个 Cookie
document.cookie = 'username=zhangsan; path=/; max-age=86400';
// 读取特定 Cookie
function getCookie(name) {
const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? decodeURIComponent(match[1]) : null;
}由于 document.cookie 的读取方式较为原始,通常需要封装辅助函数。在设置时,如果 Cookie 名称已存在则覆盖值,不存在则新增。
1.3 过期时间
Cookie 的生命周期通过两个属性控制:
Expires:指定一个绝对的到期时间戳(HTTP 日期格式)。浏览器在到期后自动删除该 Cookie。示例:Expires=Wed, 21 Oct 2025 07:28:00 GMT。Max-Age:指定从当前时刻算起的相对秒数。优先级高于Expires。值为 0 或负数表示立即删除。
如果不设置这两个属性,Cookie 为会话级 Cookie,存储在内存中,浏览器关闭即失效。
1.4 域名与路径
Domain:指定 Cookie 可被哪些域名访问。默认只对设置该 Cookie 的源域名生效。如果设为.example.com,则所有*.example.com的子域名都可访问。Path:指定 Cookie 在哪些路径下生效。只有访问该路径及其子路径时,浏览器才会发送此 Cookie。默认值为当前请求路径的目录部分。
这两个属性共同构成了 Cookie 的作用域机制。需要注意的是,子域名 Cookie 存在安全风险,如果某个子域名存在 XSS 漏洞,攻击者可能窃取父域名的 Cookie。
1.5 Secure 与 HttpOnly
Secure:标记为 Secure 的 Cookie 仅在 HTTPS 连接中传输,防止中间人攻击。设置此标记要求页面必须通过 HTTPS 访问,但请注意,Secure 只保证传输安全,不保证存储安全。HttpOnly:标记为 HttpOnly 的 Cookie 无法通过 JavaScript 的document.cookie读取,只能由服务器接收。这是防范 XSS 攻击窃取 Cookie 的重要手段,敏感信息(如会话令牌)应始终设置此标记。
1.6 SameSite
SameSite 属性用于控制第三方上下文中 Cookie 的发送行为,是防范 CSRF 攻击的关键机制:
| 属性值 | 行为 |
|---|---|
Strict | 浏览器只在同站请求中发送 Cookie,第三方网站发起的请求(如通过链接跳转)不会携带 |
Lax | 允许部分第三方 GET 请求携带 Cookie(如导航到目标网站的顶级导航),Post 等不安全请求不携带。这是 Chrome 80+ 的默认值 |
None | 同站和跨站请求都发送 Cookie,但必须同时设置 Secure(即仅限 HTTPS) |
现代浏览器(如 Chrome、Firefox)已默认将未设置 SameSite 的 Cookie 视为 Lax,这意味着开发者需要显式为跨站场景设置 SameSite=None; Secure。
1.7 容量限制
- 单个 Cookie 大小限制:约 4KB(4096 字节左右,包括名称、值和属性)
- 每个域名下 Cookie 数量限制:约 50 个(各浏览器实现略有差异)
- 总大小限制:通常为 4-8KB 左右
由于容量极小且每次 HTTP 请求都会自动携带(即使是不必要的请求),Cookie 不适合存储大量数据。其设计目的是会话管理和标识,而非数据存储。
二、LocalStorage
2.1 概述
LocalStorage 是 Web Storage API 的一部分,由 HTML5 规范引入,提供了一种键值对形式的客户端存储方案。与 Cookie 不同,LocalStorage 的数据不会自动随 HTTP 请求发送,从而避免了不必要的网络开销。
2.2 API
LocalStorage 的操作接口简洁直观:
// 存储数据
localStorage.setItem('key', 'value');
// 读取数据
const value = localStorage.getItem('key');
// 删除单个键
localStorage.removeItem('key');
// 清空所有数据
localStorage.clear();
// 获取指定索引的键名
const keyName = localStorage.key(index);
// 获取存储项数量
const count = localStorage.length;所有存储的值都会自动转换为字符串,因此存储对象时需要序列化:
const user = { name: '张三', age: 28 };
localStorage.setItem('user', JSON.stringify(user));
const retrieved = JSON.parse(localStorage.getItem('user'));2.3 作用域
LocalStorage 的作用域基于同源策略(协议 + 域名 + 端口)。同一源下的所有页面共享同一个 LocalStorage 存储空间。这意味着:
- 在不同选项卡或窗口中打开的同一源页面,共享 LocalStorage 数据
http://example.com和https://example.com不共享http://example.com:8080和http://example.com:3000不共享http://a.example.com和http://b.example.com不共享
2.4 容量限制
LocalStorage 的容量限制因浏览器而异,通常为 5-10MB:
| 浏览器 | 容量 |
|---|---|
| Chrome | 10MB |
| Firefox | 10MB |
| Safari | 5MB |
| Edge | 10MB |
| IE | 5MB |
当存储空间不足时,浏览器会抛出 QuotaExceededError 异常,应用程序需要捕获此异常并妥善处理。
2.5 同源策略
LocalStorage 严格遵循同源策略,这一点在前端安全中至关重要。即使同一 IP 地址的不同端口之间,或同一域名的 HTTP 与 HTTPS 之间,数据也是完全隔离的。同源策略确保了恶意网站无法读取其他网站的存储数据。
2.6 特点总结
- 持久性:除非开发者主动删除或用户清除浏览器数据,否则数据永不过期
- 同步操作:所有 API 都是同步的,不会阻塞 JavaScript 执行线程
- 同源共享:同源所有窗口/选项卡共享同一存储空间
- 仅存储字符串:所有类型的值都会被转为字符串
三、SessionStorage
3.1 概述
SessionStorage 与 LocalStorage 同属 Web Storage API,API 接口完全一致,但生命周期和作用域有本质区别。
3.2 API
SessionStorage 的 API 与 LocalStorage 完全相同:
// 存储
sessionStorage.setItem('key', 'value');
// 读取
sessionStorage.getItem('key');
// 删除
sessionStorage.removeItem('key');
// 清空
sessionStorage.clear();3.3 会话级别存储
SessionStorage 的生命周期是页面会话级别的:
- 数据只在当前浏览器选项卡(Tab)或窗口的有效会话期内存在
- 关闭选项卡或窗口时,SessionStorage 数据被自动清除
- 页面刷新(F5)或通过链接导航不会丢失数据
- 但通过
window.open或<a target="_blank">打开的新页面,在某些浏览器中可能不会继承父页面的 SessionStorage
3.4 选项卡隔离
SessionStorage 最显著的特点是选项卡级别的隔离:
- 即使两个选项卡打开了完全相同的 URL,它们的 SessionStorage 也是相互独立的
- 这为多选项卡场景下的数据隔离提供了天然支持
- 例如,在线编辑器中不同的文档可以在不同选项卡中编辑,互不干扰
3.5 使用场景
- 表单多步填写过程中暂存中间状态
- 单页应用中的路由级别的临时数据
- 不需要跨选项卡共享的敏感临时信息
- 页面级别的会话状态跟踪
四、IndexedDB
4.1 概述
IndexedDB 是一个浏览器内置的 NoSQL 事务型数据库系统,提供了远比 Web Storage 强大的存储能力。它支持索引、游标、事务、批量操作和复杂的查询能力,适合存储大量结构化数据。IndexedDB 是应对客户端存储需求增长的核心方案——当应用需要存储数十 MB 甚至 GB 级数据时,IndexedDB 是唯一的选择。
4.2 数据库与对象仓库
IndexedDB 的核心概念类似于传统数据库:
- 数据库(Database):每个源(origin)可以创建多个数据库,版本号用于管理数据库结构变更
- 对象仓库(Object Store):类似于关系型数据库中的"表",用于存储特定类型的数据对象
- 记录:由键(Key)和值(Value)组成,值可以是任意结构化数据
// 打开数据库(如果不存在则创建)
const request = indexedDB.open('MyAppDB', 1);
// 版本变更时创建对象仓库
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true
});
store.createIndex('name', 'name', { unique: false });
store.createIndex('email', 'email', { unique: true });
};
request.onsuccess = (event) => {
const db = event.target.result;
// 使用数据库进行读写操作
};4.3 事务
IndexedDB 的所有读写操作都必须在事务(Transaction)的上下文中执行。事务提供了数据一致性和隔离性保障:
const tx = db.transaction(['users'], 'readwrite');
const store = tx.objectStore('users');
store.add({ name: '李四', email: 'lisi@example.com', age: 25 });
tx.oncomplete = () => console.log('事务完成');
tx.onerror = (event) => console.error('事务失败', event.target.error);事务模式:
readonly:只读操作,多个事务可以并发执行readwrite:读写操作,同一时间一个对象仓库上只能有一个读写事务
4.4 索引
索引(Index)允许开发者基于对象仓库中的特定字段进行高效查询,而非仅能通过主键检索:
// 创建索引(在 onupgradeneeded 中)
const nameIndex = store.createIndex('name', 'name', { unique: false });
// 使用索引查询
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const index = store.index('name');
const request = index.getAll('李四');
request.onsuccess = () => {
console.log('查询结果:', request.result);
};支持的索引类型:
- 唯一索引(unique):确保索引字段值的唯一性
- 复合索引:基于多个字段的组合索引
- 多键索引:针对数组类型字段的索引
4.5 游标
游标(Cursor)用于遍历对象仓库或索引中的数据,支持范围查询和定向遍历:
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const range = IDBKeyRange.bound(20, 30); // 年龄在 20-30 之间
const request = store.index('age').openCursor(range);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log('用户:', cursor.value);
cursor.continue(); // 继续遍历下一条
}
};游标支持的前进方向包括 next、prev、nextunique 和 prevunique。
4.6 异步操作
IndexedDB 的所有 API 都是异步的,基于 DOM 事件模型:
// 封装为 Promise 更便于使用
function addUser(db, user) {
return new Promise((resolve, reject) => {
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
const request = store.add(user);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}异步特性意味着 IndexedDB 的操作不会阻塞 UI 线程,即使处理大量数据也不会导致页面卡顿。然而,由于其基于事件的回调风格较为原始,实际开发中通常会使用封装库(如 Dexie.js、idb)来简化操作。
4.7 容量限制
IndexedDB 的容量远大于其他客户端存储方式:
| 浏览器 | 容量 |
|---|---|
| Chrome | 磁盘剩余空间的 60%(通常可达数百 MB 至数 GB) |
| Firefox | 无硬性限制(受磁盘空间约束) |
| Safari | 约 500MB-1GB |
| Edge | 同 Chrome(基于 Chromium) |
五、Cache API
5.1 概述
Cache API 是 Service Worker 规范的一部分,为 HTTP 请求和响应提供了细粒度的缓存控制能力。它与 HTTP 缓存的不同之处在于,开发者可以通过代码精确控制缓存哪些资源、何时更新以及如何匹配请求。
5.2 基本操作
// 打开缓存(如果不存在则创建)
caches.open('my-cache-v1').then((cache) => {
// 添加资源到缓存
cache.add('/api/data.json');
cache.addAll(['/styles/main.css', '/scripts/app.js']);
// 手动存储请求与响应
cache.put('/api/data.json', new Response(JSON.stringify({ data: '...' })));
// 匹配缓存中的请求
cache.match('/api/data.json').then((response) => {
if (response) {
// 使用缓存的响应
}
});
// 删除缓存项
cache.delete('/old/api/data.json');
});
// 获取所有缓存名称
caches.keys().then((names) => console.log(names));
// 删除整个缓存
caches.delete('my-old-cache-v1');5.3 Service Worker 缓存策略
在 Service Worker 中,结合 Cache API 可以实现多种缓存策略。以下是常见的六种策略:
Cache First(缓存优先)
先查找缓存,未命中再发网络请求,并将响应存入缓存。适用于不经常变动的静态资源。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((res) => {
const cloned = res.clone();
caches.open('static-v1').then((cache) => cache.put(event.request, cloned));
return res;
});
})
);
});Network First(网络优先)
先发网络请求,失败则回退到缓存。适用于对实时性要求较高的请求。
Network Only(仅网络)
始终从网络获取,不使用缓存。适用于需要实时数据的 API 调用。
Cache Only(仅缓存)
仅从缓存读取,不发起网络请求。适用于离线时使用的预缓存资源。
Stale While Revalidate(更新验证)
立即返回缓存,同时异步发起网络请求更新缓存。适用于用户头像、文章列表等场景。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic-cache').then((cache) => {
return cache.match(event.request).then((cached) => {
const fetchPromise = fetch(event.request).then((network) => {
cache.put(event.request, network.clone());
return network;
});
return cached || fetchPromise;
});
})
);
});Fallback to Network(缓存回退)
检查缓存,如果没有则从网络加载,同时缓存新资源。适用于离线优先的应用。
5.4 CacheStorage
CacheStorage 是全局 caches 属性对应的接口,管理所有命名的 Cache 对象:
// 检查缓存是否存在
caches.has('my-cache').then((exists) => {
if (!exists) {
// 创建缓存
caches.open('my-cache');
}
});
// 匹配所有缓存中的请求
caches.match('/api/data.json').then((response) => {
// 在所有缓存中查找匹配项
});CacheStorage.match() 方法会遍历所有 Cache 对象,适合在不确定资源位于哪个缓存中时使用。
5.5 容量限制
Cache API 的容量限制与 IndexedDB 类似,因为它通常共享相同的存储池:
- Chrome:可用磁盘空间的 60%
- 各浏览器通过
navigator.storage.estimate()API 提供存储估算
navigator.storage.estimate().then(({ quota, usage }) => {
console.log(`已用: ${(usage / 1024 / 1024).toFixed(2)} MB`);
console.log(`总额度: ${(quota / 1024 / 1024).toFixed(2)} MB`);
});六、五种存储方案对比
6.1 综合对比表格
| 存储方案 | 容量 | 作用域 | 持久性 | 同步/异步 | 数据类型 | 主要用途 |
|---|---|---|---|---|---|---|
| Cookie | 约 4KB | 同源(可指定域名/路径) | 可设置过期时间(会话级或持久) | 同步 | 字符串 | 会话标识、用户追踪 |
| LocalStorage | 5-10MB | 同源,跨选项卡共享 | 持久(需手动删除) | 同步 | 字符串(键值对) | UI 偏好设置、缓存数据 |
| SessionStorage | 5-10MB | 同源,选项卡隔离 | 会话级(关闭选项卡即清除) | 同步 | 字符串(键值对) | 会话临时数据 |
| IndexedDB | 数百 MB 至数 GB | 同源 | 持久(需手动删除) | 异步 | 任意结构化数据 | 大量结构化数据、离线数据 |
| Cache API | 数百 MB 至数 GB | 同源(可通过 Service Worker 作用域扩展) | 持久(需手动删除) | 异步 | HTTP 请求/响应 | 资源缓存、离线支持 |
6.2 存储开销比较
- Cookie:每次 HTTP 请求都会自动携带,产生网络开销,不适合存储超过几百字节的数据
- LocalStorage / SessionStorage:数据仅保留在客户端,不消耗网络带宽,但同步 API 可能阻塞主线程
- IndexedDB / Cache API:异步操作,不阻塞 UI,适合处理大量数据,但 API 较为复杂
6.3 读写性能对比
从操作延迟角度来看,Web Storage(LocalStorage/SessionStorage)的读写速度最快(微秒级),因为它是简单的内存键值存储。IndexedDB 的读写性能在中低数据量下与 Web Storage 相当,但在处理大量记录时(数万条以上)优势明显。Cache API 主要针对 HTTP 资源缓存优化,对于资源文件(图片、CSS、JS)的读取速度极快。
七、存储安全与隐私考量
7.1 XSS 攻击与数据窃取
跨站脚本攻击(XSS)是客户端存储面临的最大威胁。攻击者通过注入恶意脚本,可以窃取 Cookie(非 HttpOnly)、LocalStorage 和 SessionStorage 中的数据。
防护建议:
- 对 Cookie 中的敏感信息始终设置
HttpOnly和Secure标记 - 避免在 LocalStorage 中存储明文密码、令牌等敏感信息
- 对用户输入进行严格的转义和过滤
- 实施内容安全策略(Content Security Policy, CSP)
7.2 CSRF 攻击
跨站请求伪造(CSRF)攻击利用 Cookie 自动携带的特性,诱导用户在已登录状态下发起恶意请求。
防护建议:
- 合理设置
SameSite属性(推荐使用Lax或Strict) - 使用 CSRF Token 验证请求合法性
- 关键操作实施二次验证
7.3 同源策略的重要性
同源策略是浏览器存储安全的基础。它确保了不同源的页面无法互相读取存储数据。开发者在设计跨域交互时,需要注意:
postMessage通信中验证消息来源- 不要通过
document.domain放宽同源限制(该特性已被弃用) - 谨慎使用 CORS 配置,避免过松的跨域策略
- 子域名间的 Cookie 共享存在安全风险
7.4 隐私考量
- 第三方 Cookie 限制:主流浏览器(Chrome、Safari、Firefox)正在逐步限制或淘汰第三方 Cookie,这影响了广告追踪和跨站分析等场景
- 存储清理策略:用户可随时通过浏览器设置清除所有客户端存储数据,应用应优雅处理数据丢失的情况
- 存储空间管理:大量使用客户端存储可能导致用户磁盘空间不足,应合理控制存储量
- 数据加密:浏览器内置存储机制(除 IndexedDB 外)默认不加密,敏感数据应在上层加密处理
7.5 浏览器隐私模式的影响
在浏览器的隐私/无痕模式下:
- LocalStorage 和 SessionStorage 通常可用,但浏览器关闭后数据会被彻底清除
- IndexedDB 同样可用,数据在隐私模式结束后销毁
- Cookie 在隐私模式下仍然工作,但关闭窗口后自动清除
八、实际应用场景选型建议
8.1 会话管理与用户认证
推荐方案:Cookie(配合 HttpOnly + Secure + SameSite)
会话令牌(Session Token / JWT)应存储在 Cookie 中,并设置 HttpOnly 以防止 XSS 窃取。认证信息不应存放在 LocalStorage 中,因为一旦存在 XSS 漏洞,攻击者可轻松读取。
8.2 用户偏好设置
推荐方案:LocalStorage
主题、语言、字体大小等用户偏好设置,数据量小且需持久保存,LocalStorage 是最直接的选择。也可以同步到服务器端,实现跨设备同步。
8.3 表单临时数据
推荐方案:SessionStorage
多步骤表单中的中间数据、未提交的草稿等,适合存储在 SessionStorage 中。关闭页面后自动清理,无需开发者手动管理清理逻辑。
8.4 离线数据与 PWA
推荐方案:IndexedDB + Cache API
- 离线优先应用的核心数据使用 IndexedDB 存储,如离线笔记、邮件、任务列表等
- 静态资源(HTML、CSS、JS、图片)通过 Cache API 在 Service Worker 中缓存
- 两者配合可实现完整的离线体验
8.5 大数据量本地缓存
推荐方案:IndexedDB
当需要缓存数千条甚至更多的记录时(如离线地图数据、产品目录、历史记录),IndexedDB 是唯一的选择。配合适当的索引设计和查询优化,可以获得接近本地数据库的查询性能。
8.6 实时应用状态
推荐方案:SessionStorage
多选项卡场景下的临时状态管理。例如,在线文档编辑器每个选项卡独立编辑不同文档,SessionStorage 的隔离性提供了天然的优势。
8.7 资源缓存
推荐方案:Cache API
对于图片、字体、CSS 和 JavaScript 等静态资源,Cache API 提供了最有效率的缓存方式。结合 Service Worker 的缓存策略,可以实现流畅的离线体验和加速页面加载。
8.8 存储选型决策树
数据需要发送到服务器吗?
├── 是 ──→ Cookie(会话标识)或 Fetch API(其他数据)
└── 否 ──→ 数据量大吗?
├── 是 ──→ 结构化数据?
│ ├── 是 ──→ IndexedDB
│ └── 否 ──→ Cache API(资源文件)
└── 否 ──→ 关闭页面后需要保留吗?
├── 是 ──→ LocalStorage
└── 否 ──→ SessionStorage九、总结
浏览器存储经历了从 Cookie 到 IndexedDB 的演进,每种存储方案都有其特定的设计目标和适用场景。Cookie 虽小但仍然在会话管理中不可替代,Web Storage 简洁易用适合轻量数据,IndexedDB 功能强大适合复杂客户端数据管理,Cache API 则是 PWA 离线体验的基础。理解这些存储机制的差异,能够帮助开发者做出更合理的技术决策,构建更高效、更安全的 Web 应用。
在实际开发中,往往需要组合使用多种存储方案。例如,一个典型的 PWA 应用可能同时使用 Cookie 管理会话、LocalStorage 保存偏好设置、IndexedDB 存储离线数据、Cache API 缓存静态资源——各取所长,协同工作。
对于存储安全,应始终遵循最小权限原则:不存储不必要的敏感数据、对必须存储的敏感信息实施加密、合理利用 HttpOnly 和 SameSite 等安全属性、部署 CSP 策略防范 XSS 攻击。只有在充分理解安全风险的基础上,才能构建出值得用户信任的 Web 应用。
十、Demo:存储管理器
以下是一个浏览器存储管理器的交互演示,您可以通过该工具直观地查看和操作 Cookie、LocalStorage、SessionStorage 以及了解各存储的使用情况。