返回首页

浏览器存储方案怎么选:Cookie、localStorage、IndexedDB 全梳理

Cookie、localStorage、sessionStorage、IndexedDB 各有各的容量、生命周期和适用场景,选错了要么不安全要么不够用。这篇把浏览器端的主要存储方案放在一起对比,给出一张选型表和常见误区。

41 约 5 分钟 · 5086 字 前端

"这个数据存哪儿?"是前端的日常问题。存 Cookie?localStorage?还是上 IndexedDB?选错了,轻则容量不够、性能拉胯,重则把 token 暴露在 XSS 之下。

这篇把浏览器端几种主要存储方案放在一起,讲清楚各自的边界和该用在哪。

先看全景对比

方案 容量 生命周期 是否随请求发送 可被 JS 读取 数据结构
Cookie ~4KB 可设过期时间 (每次请求自动带) 取决于 HttpOnly 字符串
localStorage ~5–10MB 永久(手动清除) 字符串
sessionStorage ~5–10MB 标签页关闭即清 字符串
IndexedDB 通常数百 MB 起 永久 结构化对象
Cache Storage 受配额限制 永久 Request/Response

下面逐个说它们的关键特性和适用场景。

Cookie:为"跟服务器交互"而生

Cookie 最大的特点是:它会在每次同源请求时自动加到请求头里发给服务器。这既是它的用途,也是它的负担。

// 设置(注意:这是不安全的客户端写法,仅演示)
document.cookie = 'theme=dark; max-age=2592000; path=/; SameSite=Lax'

// 读取(原生 API 很难用,只能拿到一整个字符串)
console.log(document.cookie) // "theme=dark; other=value"

关键点:容量极小(约 4KB),且每个请求都会携带。 所以别往 Cookie 里塞大数据——每加 1KB,你的每个请求都多 1KB 流量。

Cookie 的几个安全相关属性非常重要:

  • HttpOnly:设了之后 JS 读不到(document.cookie 拿不到),能有效防止 XSS 窃取。存 token / session 必须加。
  • Secure:只在 HTTPS 下发送。
  • SameSite:控制跨站请求是否携带,Lax / Strict 能缓解 CSRF。

结论:Cookie 适合存需要服务器读取的、小体积的身份凭证(session id / token),而且这类凭证应该由服务端通过 Set-CookieHttpOnly 下发,前端不要用 JS 存敏感凭证。

localStorage:简单的键值持久化

localStorage 是最常用的客户端存储:同步 API、键值对、容量 5–10MB、永久保存(除非手动清除)。

localStorage.setItem('theme', 'dark')
localStorage.getItem('theme')      // 'dark'
localStorage.removeItem('theme')
localStorage.clear()

// 只能存字符串,对象要序列化
localStorage.setItem('user', JSON.stringify({ name: 'Tom' }))
const user = JSON.parse(localStorage.getItem('user') || '{}')

适合存:用户偏好(主题、语言)、非敏感的表单草稿、一些不重要的缓存。

要注意两点:

  1. 它是同步的,会阻塞主线程。存大量数据或频繁读写会影响性能。
  2. 只能存字符串,对象要 JSON.stringify / JSON.parse,且无法存 Date、Map 等(同 JSON 的局限)。

还有一个实用特性:storage 事件。同源的其它标签页修改 localStorage 时,当前页会收到事件,可用于多标签页同步(比如一个标签退出登录,其它标签也跟着退):

window.addEventListener('storage', (e) => {
  if (e.key === 'token' && !e.newValue) {
    // 另一个标签页清除了 token,这里同步登出
    location.reload()
  }
})

注意:storage 事件只在其它标签页触发,自己改自己是收不到的。

sessionStorage:只活在当前标签页

API 和 localStorage 一模一样,唯一区别是生命周期:数据只在当前标签页有效,关闭标签页就清除。

sessionStorage.setItem('step', '2')

它的作用域是"标签页级"的:

  • 同一个标签页内刷新、跳转,数据还在。
  • 新开一个标签页(即使同源),是独立的、空的 sessionStorage。
  • 关闭标签页,数据消失。

适合存:多步表单的中间状态、一次性的页面间传参、"本次会话"才有意义的临时数据。需要"换个标签页就重来"的场景,它比 localStorage 更合适。

IndexedDB:浏览器里的数据库

当数据量大、结构复杂、需要查询时,localStorage 就不够了。IndexedDB 是浏览器内置的事务型、异步、能存结构化对象的数据库。

它的特点:

  • 容量大:通常几百 MB 起步,按可用磁盘动态分配。
  • 异步:不阻塞主线程,适合大数据。
  • 存结构化数据:能直接存对象、数组、Blob、File,不用手动序列化成字符串。
  • 支持索引和事务:能按字段查询。

原生 API 偏底层、基于事件,写起来啰嗦:

const req = indexedDB.open('myDB', 1)

req.onupgradeneeded = (e) => {
  const db = e.target.result
  db.createObjectStore('posts', { keyPath: 'id' })
}

req.onsuccess = (e) => {
  const db = e.target.result
  const tx = db.transaction('posts', 'readwrite')
  tx.objectStore('posts').put({ id: 1, title: '一篇文章' })
}

实战里基本不会直接用原生 API,而是用封装库(比如 idblocalforage)。localforage 甚至提供和 localStorage 几乎一样的 API,底层自动用 IndexedDB:

import localforage from 'localforage'

await localforage.setItem('user', { name: 'Tom' }) // 直接存对象,异步
const user = await localforage.getItem('user')

适合存:离线数据、大量列表缓存、富文本编辑器内容、PWA 离线资源等。

Cache Storage:配合 Service Worker

Cache Storage 专门用来缓存网络请求的响应(Request/Response 对),是 PWA 离线能力的基础,一般配合 Service Worker 使用:

const cache = await caches.open('v1')
await cache.add('/index.html')
const res = await cache.match('/index.html')

它不是用来存普通业务数据的,而是缓存静态资源和接口响应,实现离线访问。日常业务存储用不到它,做 PWA 时才需要。

一张选型决策表

你的需求 推荐方案
身份凭证 / session(要给服务器读) Cookie(HttpOnly + Secure + SameSite)
主题、语言等用户偏好 localStorage
多步表单、仅本标签页有效的临时数据 sessionStorage
大量结构化数据、离线缓存、需要查询 IndexedDB(用 idb / localforage 封装)
缓存请求响应、PWA 离线 Cache Storage + Service Worker

安全与常见误区

第一,别把 token 存 localStorage。 这是最常见的错误。localStorage 能被任意 JS 读取,一旦站点有 XSS,token 就被偷了。敏感凭证应放在 HttpOnly Cookie 里,JS 碰不到。

第二,localStorage 不是缓存层,没有过期机制。 它不会自动过期,要"带过期时间"得自己存一个时间戳,读取时判断:

function setWithTTL(key, value, ttlMs) {
  localStorage.setItem(key, JSON.stringify({ value, expire: Date.now() + ttlMs }))
}
function getWithTTL(key) {
  const raw = localStorage.getItem(key)
  if (!raw) return null
  const { value, expire } = JSON.parse(raw)
  if (Date.now() > expire) {
    localStorage.removeItem(key)
    return null
  }
  return value
}

第三,别在 localStorage 里存大数据。 它是同步的,存几 MB 的 JSON 会卡主线程。大数据用 IndexedDB。

第四,存储都是按源(origin)隔离的。 httphttps、不同端口、不同域名,存储互不相通。调试时别把这点忘了。

第五,容量会满,要处理写入失败。 localStorage 写满会抛 QuotaExceededError,关键写入要 try/catch。

总结

浏览器存储选型,核心就两个维度:数据要不要给服务器数据有多大/多复杂

  • 要给服务器、且是凭证 → Cookie(带 HttpOnly)。
  • 小、纯客户端、要持久 → localStorage。
  • 小、纯客户端、只活在本标签页 → sessionStorage。
  • 大、结构化、要查询或离线 → IndexedDB。

记牢一条安全红线:敏感凭证只进 HttpOnly Cookie,永远别进 localStorage。把这些理清楚,"数据存哪儿"就不再是拍脑袋的决定了。