浏览器存储
面试速答(30 秒版 TL;DR)
- 浏览器存储不是单一能力,而是一组分层方案:Cookie、Web Storage、IndexedDB、Cache API 各管一类问题。
- 先记住一句话:要随请求发给服务端,用 Cookie;只在前端留少量字符串,用
localStorage/sessionStorage;要存大体积结构化数据,用 IndexedDB;要缓存请求和响应,用 Cache API。 - 真正的工程难点不是“API 会不会用”,而是:作用域、同步/异步、容量、淘汰策略、安全边界。
- 一句话记忆:浏览器存储选型,本质是“谁来读、存多久、数据多大、会不会跟请求走”。
心智模型:浏览器里其实有四类完全不同的存储
很多人把“浏览器存储”直接等同于 localStorage,这是不对的。
前端真正高频会碰到的是四类能力:
- Cookie:更像“浏览器帮你随请求携带的一小段状态”
- Web Storage:
localStorage/sessionStorage,更像“页面直接可读写的小型键值表” - IndexedDB:更像“浏览器内置的本地事务型数据库”
- Cache API:更像“面向
Request/Response的离线资源仓库”
它们不是彼此替代关系,而是解决问题的维度不同。
一张图看懂怎么选
先抓住 3 个判断:要不要随请求走、是不是轻量键值、是不是请求响应缓存。
选型总表
| 方案 | 典型容量感知 | 生命周期 / 作用域 | 读写方式 | 典型用途 | 最大风险 |
|---|---|---|---|---|---|
| Cookie | 很小,通常是 KB 级 | 同站点请求可自动携带,可设置过期时间 | 字符串 | 会话标识、服务端要读取的轻量状态 | 每次请求都可能被带上,网络和安全成本高 |
sessionStorage | 小到中等 | 同源 + 同标签页,关页即失效 | 同步、字符串 | 临时表单态、当前页流程状态 | 不能跨标签页共享,大数据会阻塞主线程 |
localStorage | 小到中等 | 同源,浏览器重开后通常还在 | 同步、字符串 | 轻量偏好、少量缓存、跨标签页同步信号 | 同步 API,写大对象容易卡顿 |
| IndexedDB | 大得多,适合结构化数据 | 同源 | 异步、事务、可索引 | 离线数据、草稿箱、结构化业务缓存 | API 心智成本高,版本升级要设计 |
| Cache API | 适合缓存 Request/Response | 同源 | 异步 | PWA 资源缓存、离线兜底 | 不是 HTTP 缓存替身,需要自己做版本管理 |
1. Cookie:它首先是“请求状态”,不是“前端缓存”
什么时候该用 Cookie
适合这类场景:
- 服务端需要在每次请求里自动拿到状态
- 要做会话维持
- 需要结合
HttpOnly、Secure、SameSite等属性控制安全行为
最典型的是:
- Session ID
- 某些服务端要读的轻量用户偏好
- CSRF / 登录态相关状态
为什么 Cookie 不适合存业务缓存
因为它和其它存储最本质的区别是:
- 浏览器通常会把它放进后续请求头里
所以一旦你把大 JSON、权限树、商品列表塞进 Cookie,就会引发:
- 请求头膨胀
- 弱网下时延增加
- 静态资源也可能被无意义地携带 Cookie
因此更稳的面试表达是:
Cookie 是“随请求发送的状态容器”,不是“前端本地缓存仓库”。
2. Web Storage:好用,但别把它当本地数据库
MDN 对 Web Storage 的定义很直接:它提供比 Cookie 更直观的键值存储能力;其中 sessionStorage 按标签页 + 同源隔离,localStorage 按同源隔离。
sessionStorage
特点:
- 只在当前标签页会话里有效
- 关闭标签页后数据消失
- 适合“当前页流程态”
常见场景:
- 多步骤表单中间态
- 当前页筛选条件
- 一次性跳转参数缓存
localStorage
特点:
- 同源下多个标签页共享
- 浏览器关闭后通常仍会保留
- 可以借助
storage事件做简单跨标签页同步
常见场景:
- 主题设置
- 语言偏好
- 少量、低频更新的前端缓存
Web Storage 最大的坑:它是同步 API
这是面试里很容易加分的一点。
虽然 localStorage 很顺手,但它的问题是:
- 读写发生在主线程
- 只能存字符串
- 大对象需要
JSON.stringify/JSON.parse
所以不适合:
- 大体积数据
- 高频写入
- 复杂结构查询
最小示例:
localStorage.setItem('theme', 'dark')
const theme = localStorage.getItem('theme')
console.log(theme)
跨标签页同步示例:
window.addEventListener('storage', (event) => {
if (event.key === 'token' && event.newValue === null) {
console.log('其它标签页已退出登录')
}
})
这里要注意:
- 触发
storage事件的是其它同源页面 - 当前写入页面本身不会因为这次写操作收到该事件
3. IndexedDB:真正适合大体积结构化数据
如果你的需求已经变成:
- 要存大量结构化数据
- 需要索引和范围查询
- 需要事务
- 需要更稳定的离线数据能力
那答案通常不是 localStorage,而是 IndexedDB。
MDN 明确把 IndexedDB 描述为:面向大量结构化数据的低层客户端存储 API,并且支持索引和事务。
它适合什么场景
- 离线草稿
- IM/邮件类本地消息缓存
- 大量列表数据离线存储
- 需要按主键或索引查找的数据
它为什么更强
因为它具备:
- 异步读写,不直接阻塞主线程
- 可存结构化对象,不限于字符串
- 事务能力
- 索引能力
但它也更重
工程上要考虑:
- 数据库版本升级
onupgradeneeded的 schema 迁移- 事务范围和错误处理
一个极简示例:
const request = indexedDB.open('app-db', 1)
request.onupgradeneeded = () => {
const db = request.result
db.createObjectStore('articles', { keyPath: 'id' })
}
request.onsuccess = () => {
const db = request.result
const tx = db.transaction('articles', 'readwrite')
tx.objectStore('articles').put({ id: 1, title: '浏览器存储' })
}
面试里不用把 API 细节背得很碎,但要能说出:
IndexedDB 解决的是
localStorage在容量、结构化查询、事务和异步能力上的短板。
4. Cache API:缓存的是“请求与响应”,不是普通业务对象
很多人第一次接触 Cache API 是在 Service Worker 里,但它不是只给 Service Worker 用的。
它的本质是:
- 以
Request/Response为核心的数据存储
典型用途:
- 离线页面兜底
- 静态资源预缓存
- PWA 资源缓存
最小示例:
const cache = await caches.open('assets-v1')
await cache.add('/offline.html')
const response = await cache.match('/offline.html')
它和 HTTP 缓存不是一回事
MDN 特别提醒了一点:
- Cache API 不会自动遵守 HTTP 缓存头语义
这意味着:
- 你要自己决定版本号
- 你要自己清老缓存
- 你要自己控制更新策略
所以 Cache API 更像“你自己管理的离线资源仓库”,不是浏览器自动帮你维护的强一致缓存层。
配额与淘汰:浏览器存储不是“写进去就永远在”
这是面试里经常被忽略,但非常体现工程视角的一段。
先记住两个结论
- 不同浏览器的容量上限和淘汰策略不同,不要背死数字。
- 浏览器在存储压力下,可能会清理某个 origin 的整组数据。
MDN 的 Storage API 文档强调:浏览器可以通过 navigator.storage.estimate() 查看大致使用量;在存储压力下,最佳努力型数据可能被淘汰;Safari 还可能对长期未交互站点的脚本写入数据做主动清理。
推荐表达方式
比起说“localStorage 就是 5MB”,更稳妥的说法是:
Web Storage 往往适合小体量数据;更大规模的本地数据通常交给 IndexedDB 或 Cache API;但具体配额和淘汰策略依赖浏览器实现,需要以实际环境验证。
一个实用检测示例
if (navigator.storage?.estimate) {
const { usage, quota } = await navigator.storage.estimate()
console.log({ usage, quota })
}
如果业务非常依赖离线数据,还可以尝试:
if (navigator.storage?.persist) {
const granted = await navigator.storage.persist()
console.log('是否获得持久存储', granted)
}
但要注意:
persist()只是请求,不是一定成功- 各浏览器是否授予、如何授予,存在实现差异
典型场景怎么选
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 登录会话标识 | Cookie | 服务端要自动读取,请求会携带 |
| 当前标签页临时草稿 | sessionStorage | 关闭标签页即结束,隔离性强 |
| 主题、语言、少量用户偏好 | localStorage | 简单直接,同源多标签页可共享 |
| 离线文章、草稿箱、消息列表 | IndexedDB | 数据大、结构化、需要异步和索引 |
| 离线资源页、PWA 预缓存 | Cache API | 面向 Request/Response 的缓存能力 |
典型题 & 标准答法
Q1:Cookie、localStorage、sessionStorage 的区别是什么?
答:最核心的区别有三点。第一,Cookie 通常会随同站点请求自动发送,localStorage/sessionStorage 不会;第二,Cookie 容量很小且偏服务端状态,Web Storage 更适合前端小型键值存储;第三,sessionStorage 是同源且同标签页隔离,关闭标签页即失效,而 localStorage 是同源共享,浏览器重开后通常仍存在。
Q2:为什么大数据不建议放 localStorage?
答:因为 localStorage 是同步字符串存储。大对象需要序列化和反序列化,读写发生在主线程,容易卡顿,而且不支持索引、事务和复杂查询。大体量结构化数据更适合 IndexedDB。
Q3:Cache API 和浏览器 HTTP 缓存有什么区别?
答:HTTP 缓存是浏览器按协议和响应头自动管理;Cache API 是开发者手动维护的 Request/Response 存储。Cache API 更灵活,适合离线资源管理,但不会自动遵守 HTTP 缓存头,需要自己做版本和淘汰策略。
Q4:浏览器存储会不会被清掉?
答:会。不同浏览器有自己的配额和淘汰策略。在存储压力下,非持久化数据可能被回收;Safari 还可能对长期未交互站点主动清理脚本写入数据。所以不能把浏览器存储理解成永不丢失的本地硬盘。
常见追问
- 为什么说
localStorage适合“低频、小量、简单”数据? - IndexedDB 为什么更适合离线优先应用?
storage事件能不能做跨标签页通信?- 第三方 iframe 为什么越来越难直接读写存储?
易错点
- 不要把 Cookie 当普通缓存。它最大的特征是可能随请求自动发送。
- 不要把
localStorage当数据库。它没有事务、索引,也不是异步的。 - 不要说“浏览器本地存储永远不会丢”。配额和淘汰策略都受浏览器实现影响。
- 不要把 Cache API 和 HTTP 缓存混为一谈。两者的控制权和语义完全不同。
速记要点
- Cookie:给服务端看的轻量状态。
- Web Storage:给页面自己用的小型键值存储。
- IndexedDB:给大体量结构化数据用的本地数据库。
- Cache API:给离线资源和请求响应缓存用。
- 选型看四件事:谁来读、存多久、数据多大、会不会跟请求走。