Web 存储方案

localStorage、sessionStorage、Cookies 和 IndexedDB 的对比、适用场景与安全边界。

#tech / dev / frontend #resource / browser #type / concept #status / growing

[!info] related notes

Web 存储方案

定义

浏览器提供了多种客户端存储机制,适用于不同的数据规模、生命周期和使用场景。

方案对比

localStoragesessionStorageCookiesIndexedDB
生命周期永久(手动清除)标签页关闭即清除可设置过期时间永久(手动清除)
容量~5-10MB~5-10MB~4KB几百 MB 甚至更多
与请求的关系不自动发送不自动发送每次请求自动携带不自动发送
API同步,简单同步,简单需手动解析异步,复杂
数据格式字符串字符串字符串结构化数据(对象、文件)
适用场景用户偏好、主题、缓存表单暂存、临时状态认证 token、会话标识大量数据、离线应用

localStorage

// 存储
localStorage.setItem('theme', 'dark')

// 读取
const theme = localStorage.getItem('theme')

// 删除
localStorage.removeItem('theme')

// 清空所有
localStorage.clear()

存储对象需要序列化:

const user = { name: 'Alice', age: 25 }
localStorage.setItem('user', JSON.stringify(user))

const parsed = JSON.parse(localStorage.getItem('user'))

跨标签页同步(同源):

// 监听其他标签页的变化
window.addEventListener('storage', (e) => {
  console.log(e.key)       // 变化的键
  console.log(e.oldValue)  // 旧值
  console.log(e.newValue)  // 新值
  console.log(e.url)       // 触发变化的页面 URL
})

sessionStorage

API 与 localStorage 完全相同:

sessionStorage.setItem('form-draft', JSON.stringify(formData))
const draft = JSON.parse(sessionStorage.getItem('form-draft'))

关键区别:

  • 仅在当前标签页有效,关闭标签页即清除
  • 同源的不同标签页之间不共享
  • 页面刷新后保留(因为标签页没关闭)

Cookies

// 设置 cookie(注意:原生 API 很难用)
document.cookie = 'username=alice; path=/; max-age=86400; Secure; SameSite=Strict'
document.cookie = 'token=abc123; path=/; expires=Fri, 31 Dec 2026 23:59:59 GMT'

// 读取(需要手动解析)
const cookies = document.cookie.split(';').reduce((acc, cookie) => {
  const [key, value] = cookie.trim().split('=')
  acc[key] = value
  return acc
}, {})
  • path:cookie 的作用路径
  • domain:cookie 的作用域
  • max-age:存活秒数
  • expires:过期时间点
  • Secure:只通过 HTTPS 发送
  • HttpOnly:JS 无法访问(防 XSS)
  • SameSiteStrict / Lax / None(防 CSRF)
// 推荐库:js-cookie
import Cookies from 'js-cookie'
Cookies.set('name', 'value', { expires: 7, path: '' })
Cookies.get('name')
Cookies.remove('name')

IndexedDB

// 打开数据库
const request = indexedDB.open('myDB', 1)

request.onupgradeneeded = (event) => {
  const db = event.target.result
  // 创建对象仓库(类似表)
  const store = db.createObjectStore('users', { keyPath: 'id' })
  store.createIndex('name', 'name', { unique: false })
}

request.onsuccess = (event) => {
  const db = event.target.result

  // 写入数据
  const tx = db.transaction('users', 'readwrite')
  const store = tx.objectStore('users')
  store.add({ id: 1, name: 'Alice', age: 25 })

  // 读取数据
  const getRequest = store.get(1)
  getRequest.onsuccess = () => {
    console.log(getRequest.result)
  }
}

推荐使用封装库简化操作:

// 使用 idb(Jake Archibald 的轻量封装)
import { openDB } from 'idb'

const db = await openDB('myDB', 1, {
  upgrade(db) {
    db.createObjectStore('users', { keyPath: 'id' })
  }
})

await db.put('users', { id: 1, name: 'Alice' })
const user = await db.get('users', 1)

安全性

XSS 风险

  • localStorage 和 sessionStorage 可以被 JS 直接访问,如果页面有 XSS 漏洞,存储的数据会被窃取
  • Cookie 设置 HttpOnly 后 JS 无法访问,更安全
  • 敏感数据(如 token)优先放在 HttpOnly Cookie 中

CSRF 风险

  • Cookie 会自动随请求发送,可能被 CSRF 攻击利用
  • 设置 SameSite=StrictSameSite=Lax 可以缓解

最佳实践

  • 认证 token → HttpOnly + Secure + SameSite Cookie
  • 非敏感的用户偏好 → localStorage
  • 临时表单数据 → sessionStorage
  • 大量离线数据 → IndexedDB
  • 不要在客户端存储敏感信息(密码、信用卡号等)

边界

  • localStorage 是同步 API,存储大数据可能阻塞主线程
  • 隐身模式下 localStorage 可能不可用(部分浏览器会抛错)
  • localStorage 的容量限制因浏览器而异(通常 5-10MB)
  • Cookie 的 4KB 限制是整个 cookie 字符串(包括键、值、属性)
  • IndexedDB 的操作都是异步的,需要用事件或 Promise 处理
  • Service Worker 可以使用 Cache Storage API 缓存网络请求

一句话记忆法

小数据用 localStorage(持久)或 sessionStorage(会话级),认证用 HttpOnly Cookie,大量结构化数据用 IndexedDB。

创建于 2026/5/27 更新于 2026/5/27