Skip to main content
选存储说到底是先看数据量、生命周期和要不要同步到服务端,这三点定了,方案基本就定了。

1

1994年: Cookie 的诞生

Netscape 引入 Cookie 解决 HTTP 无状态的问题——服务器需要一种机制来识别用户。Cookie 的核心设计是每次 HTTP 请求自动携带,让服务器能通过 Cookie 识别用户身份。
2

致命痛点: Cookie 的局限

Cookie 单条约 4KB(4096 字节,含名值),每次请求都增加带宽,且有 CSRF 风险。对于大量客户端数据(如离线缓存、用户偏好设置),Cookie 力不从心。
3

HTML5 的存储革命: Web Storage

HTML5 引入了 LocalStorage(永久存储,通常 5-10MB)和 SessionStorage(标签页隔离,通常 5-10MB),同步 API,使用简单。但它们只能存字符串,无法存储结构化数据,且同步读写会阻塞主线程。
4

IndexedDB: 大规模结构化数据

IndexedDB 是浏览器内置的 NoSQL 数据库,支持事务、索引、游标,容量受浏览器配额限制(通常远大于 Web Storage)。异步 API 不阻塞主线程,适合大量结构化数据。但 API 复杂,通常用 idb 或 localForage 封装。
5

关键权衡: 数据量, 生命周期, 服务端同步

选择存储方案要看三个维度:数据量决定容量需求,生命周期决定持久化策略,是否需要和服务端同步决定用 Cookie 还是客户端存储。
前端存储解决的核心问题:
一句话总结:选择存储方案的核心是匹配数据特征——小量认证数据用 Cookie,中量键值数据用 Web Storage,大量结构化数据用 IndexedDB。

不区分存储方案

就像一个仓库里所有东西都堆在门口的纸箱里(Cookie)。纸箱只有 4KB 空间,每次发货都要把纸箱里的东西一起寄出去(每次请求自动携带),仓库里堆了几 TB 的数据根本放不下。

按需求选择存储方案

就像一个现代化仓库有了分区管理:门口放快递单(Cookie,小但自动随货发送),货架放常用物品(LocalStorage,中等容量随时取用),档案室放大量文件(IndexedDB,容量大但需要按流程存取)。每个区域的大小、访问方式、生命周期都不同,各司其职。
回答时先按”数据量→生命周期→是否需要服务端同步”三个维度分析,再给出具体方案。想听的是你的选型思路,而不是罗列 API 参数。

Cookie 的本质是服务器通过 HTTP 响应头写入、浏览器通过 HTTP 请求头携带的键值对。核心机制只有 3 步:
Cookie 的安全属性是防御 XSS 和 CSRF 的关键:
  • HttpOnly:JS 无法通过 document.cookie 读取,防御 XSS 窃取 Token。
  • Secure:只在 HTTPS 连接下发送,防御中间人攻击。
  • SameSite:控制跨站请求时是否发送 Cookie,防御 CSRF。Chrome 默认 Lax。
Cookie 就像快递包裹上的面单。服务器(发件人)在包裹上贴好面单(Set-Cookie),浏览器(快递公司)记住面单信息,之后每次给这个收件人送包裹都自动贴上相同的面单(自动携带 Cookie)。HttpOnly 相当于面单上贴了”仅限内部使用”标签——快递员(JS)看不到面单内容。SameSite=Strict 相当于”只接受本人签收”——别人代签(跨站请求)直接拒绝。

Q3: LocalStorage 和 IndexedDB 有什么区别?

基础梳理

专家视角深度剖析

很多人能说出 LocalStorage 和 IndexedDB 的区别,但实际更在意的是:在什么场景下必须用 IndexedDB?LocalStorage 的同步 API 会带来什么问题? LocalStorage 是同步 API——读写操作会阻塞主线程。对于小量数据(几 KB),阻塞时间可以忽略。但如果存储了 1MB 以上的数据,读写操作可能导致明显的卡顿。Chrome 的 LocalStorage 底层用 LevelDB 实现,按 origin(协议+域名+端口)隔离。 IndexedDB 是异步 API——操作在浏览器内部的独立线程执行,不占用页面主线程。它支持事务(自动提交)、索引(多字段查询)、游标(遍历大数据集),适合大量结构化数据。但 IndexedDB 的 API 非常复杂,通常用 idb(Jake Archibald 的封装库)或 localForage(提供类似 LocalStorage 的简单 API)。

Q4: SessionStorage 有什么特殊行为?

SessionStorage 的核心特点:标签页隔离。 同一个 URL 在不同标签页有独立的 SessionStorage——这和 Cookie/LocalStorage 不同,它们在同一域名下共享。 关键行为:
SessionStorage 适合存储临时性的页面状态——表单暂存、多步表单的中间状态、当前页的筛选条件。不要用它存储需要跨标签页共享的数据(用 LocalStorage),也不要存储需要持久化的数据(用 LocalStorage 或 IndexedDB)。注意:window.open 打开的新窗口会复制(不是共享)当前的 SessionStorage。复制后两个窗口的 SessionStorage 是独立的,互不影响。

底层源码:Chromium 是如何实现存储的?

Chromium 的存储架构统一由 StoragePartition 管理(详见 Chromium 源码:storage_partition_impl.cc):
存储后端统一由 StoragePartitionImpl 管理。LocalStorage 和 IndexedDB 底层都用 LevelDB(Google 的键值存储引擎),但使用方式不同:LocalStorage 是同步 API,在渲染进程主线程执行;IndexedDB 是异步 API,在独立线程执行,不阻塞主线程。 详见 Chromium 源码:quota_settings.cc:
存储配额由 Chromium 的 Quota 系统统一管理。LocalStorage 的 5MB 限制是每个 origin 独立的。IndexedDB 和 Cache Storage 共享配额池,Chrome 中单源总配额通常不超过磁盘总量的约 60%,具体随磁盘剩余空间动态调整。当配额不足时,setItem 和事务操作会抛出 QuotaExceededError。可以用 navigator.storage.estimate() 查询已用空间和总配额,用 navigator.storage.persist() 申请持久化存储——它返回 Promise<boolean>,成功后数据不会被浏览器在空间紧张时自动清理。

及格回答 vs 满分回答

问题:“说说前端存储方案的区别”

及格回答(60-70分):
“Cookie 单条约 4KB,每次请求自动携带。LocalStorage 5MB 左右,永久存储。SessionStorage 关闭标签页就没了。IndexedDB 受配额限制,异步 API。”
问题出在:背了参数但不理解选型逻辑。 满分回答(90-100分):
选择存储方案要看三个维度:数据量、生命周期、是否需要和服务端同步。Cookie 的核心价值是自动携带——每次 HTTP 请求都会带上,适合身份认证。但 Cookie 单条约 4KB(4096 字节,含名值),每次请求增加带宽,且有 CSRF 风险。现代方案常用 JWT + Authorization Header 替代 Cookie 做认证。[1]
LocalStorage 是同步 API——读写操作会阻塞主线程。对于小量数据(几 KB)没问题,但存储 1MB 以上数据时读写可能导致卡顿。SessionStorage 的特点是标签页隔离——同一 URL 在不同标签页有独立的 SessionStorage,适合表单暂存。window.open 打开的新窗口会复制(不是共享)SessionStorage。[2]
IndexedDB 是异步 API,适合大量结构化数据。支持事务(自动提交)、索引(多字段查询)、游标(遍历大数据集)。但 API 复杂,通常用 idb 或 localForage 封装。IndexedDB 的事务有一个坑:所有请求完成后自动提交,不能像 SQL 那样手动 begin/commit。如果事务中 await 了非 IndexedDB 的异步操作(如网络请求),事务可能已经自动提交了。[3]
Chromium 中 LocalStorage 和 IndexedDB 底层都用 LevelDB,但 LocalStorage 是同步 API 在主线程执行,IndexedDB 是异步 API 在独立线程执行。存储配额由 Quota 系统统一管理,LocalStorage 每个 origin 约 5MB,IndexedDB 和 Cache Storage 共享配额池,Chrome 中单源总配额通常不超过磁盘总量的约 60%。配额不足时 setItem 会抛 QuotaExceededError。

常见追问

题目来源于真实面经(字节/腾讯/阿里前端面试、牛客网、haizlin/fe-interview),按难度递进。
答题要点: 结合具体错误场景和解决方案解释。高分回答: “LocalStorage 存满时,setItem 会抛出 QuotaExceededError。不同浏览器限制不同——通常是 5MB-10MB,按 origin 隔离。处理方式: 1)预防:用 navigator.storage.estimate() 查询已用空间和配额,在接近上限时主动清理。 2)捕获异常:try-catch 包裹 setItem,在捕获到 QuotaExceededError 时清理过期数据。 3)降级方案:数据量大时迁移到 IndexedDB——它受 Quota 系统统一管理,单源配额通常远大于 LocalStorage。
注意:旧版 Safari 的无痕模式曾把 LocalStorage 配额限制为 0,任何写入都会抛 QuotaExceededError;不同版本行为可能变化,仍需 try-catch 兜底。”
答题要点: 指出自动提交机制和异步操作的冲突。高分回答: “IndexedDB 的事务有两个核心特点:1)自动提交:当所有请求完成后、没有新的请求时,事务自动提交。不能像 SQL 那样手动 begin/commit/rollback。这是为了简化 API,但也是最容易踩坑的地方。2)异步操作的坑:如果事务中 await 了非 IndexedDB 的异步操作(如网络请求),事务可能在等待期间自动提交。
事务失败时自动回滚。可以通过 tx.abort() 手动回滚;用 idb 封装时也可以 await tx.done 等待完成。”
答题要点: 对比两种缓存机制的控制权和适用场景。高分回答: “Service Worker 的 Cache Storage 和浏览器 HTTP 缓存是两套独立的系统:HTTP 缓存:由浏览器自动管理,遵循 Cache-Control、ETag 等响应头。开发者只能通过 HTTP 头控制缓存策略,无法用 JS 精细控制。适用于静态资源的自动缓存。Cache Storage:由 Service Worker 通过 Cache API 完全控制。开发者可以精确决定缓存什么、什么时候缓存、什么时候清除。适用于离线应用(PWA)和自定义缓存策略。关键区别: 1)控制权:HTTP 缓存由浏览器自动管理,Cache Storage 由 JS 完全控制。 2)生命周期:HTTP 缓存随浏览器清理,Cache Storage 可以持久化。 3)作用域:HTTP 缓存在网络层拦截,Cache Storage 在 Service Worker 中拦截。 4)配合使用:Service Worker 可以先查 Cache Storage,未命中再走网络请求(自动触发 HTTP 缓存)。典型的 PWA 缓存策略:Service Worker 在 install 事件中预缓存关键资源,在 fetch 事件中拦截请求,优先返回缓存内容(Cache-First),或者先返回缓存再更新(Stale-While-Revalidate)。“

延伸阅读与引用参考