跳到主要内容

浏览器存储

面试速答(30 秒版 TL;DR)

  • 浏览器存储不是单一能力,而是一组分层方案:Cookie、Web Storage、IndexedDB、Cache API 各管一类问题。
  • 先记住一句话:要随请求发给服务端,用 Cookie;只在前端留少量字符串,用 localStorage/sessionStorage;要存大体积结构化数据,用 IndexedDB;要缓存请求和响应,用 Cache API。
  • 真正的工程难点不是“API 会不会用”,而是:作用域、同步/异步、容量、淘汰策略、安全边界
  • 一句话记忆:浏览器存储选型,本质是“谁来读、存多久、数据多大、会不会跟请求走”。

心智模型:浏览器里其实有四类完全不同的存储

很多人把“浏览器存储”直接等同于 localStorage,这是不对的。

前端真正高频会碰到的是四类能力:

  • Cookie:更像“浏览器帮你随请求携带的一小段状态”
  • Web StoragelocalStorage / sessionStorage,更像“页面直接可读写的小型键值表”
  • IndexedDB:更像“浏览器内置的本地事务型数据库”
  • Cache API:更像“面向 Request/Response 的离线资源仓库”

它们不是彼此替代关系,而是解决问题的维度不同。


一张图看懂怎么选

先抓住 3 个判断:要不要随请求走、是不是轻量键值、是不是请求响应缓存。


选型总表

方案典型容量感知生命周期 / 作用域读写方式典型用途最大风险
Cookie很小,通常是 KB 级同站点请求可自动携带,可设置过期时间字符串会话标识、服务端要读取的轻量状态每次请求都可能被带上,网络和安全成本高
sessionStorage小到中等同源 + 同标签页,关页即失效同步、字符串临时表单态、当前页流程状态不能跨标签页共享,大数据会阻塞主线程
localStorage小到中等同源,浏览器重开后通常还在同步、字符串轻量偏好、少量缓存、跨标签页同步信号同步 API,写大对象容易卡顿
IndexedDB大得多,适合结构化数据同源异步、事务、可索引离线数据、草稿箱、结构化业务缓存API 心智成本高,版本升级要设计
Cache API适合缓存 Request/Response同源异步PWA 资源缓存、离线兜底不是 HTTP 缓存替身,需要自己做版本管理

1. Cookie:它首先是“请求状态”,不是“前端缓存”

适合这类场景:

  • 服务端需要在每次请求里自动拿到状态
  • 要做会话维持
  • 需要结合 HttpOnlySecureSameSite 等属性控制安全行为

最典型的是:

  • Session ID
  • 某些服务端要读的轻量用户偏好
  • CSRF / 登录态相关状态

因为它和其它存储最本质的区别是:

  • 浏览器通常会把它放进后续请求头里

所以一旦你把大 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、localStoragesessionStorage 的区别是什么?

:最核心的区别有三点。第一,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:给离线资源和请求响应缓存用。
  • 选型看四件事:谁来读、存多久、数据多大、会不会跟请求走。