Web 存储方案
localStorage、sessionStorage、Cookies 和 IndexedDB 的对比、适用场景与安全边界。
#tech / dev / frontend
#resource / browser
#type / concept
#status / growing
[!info] related notes
- 所属 MOC: 前端基础 MOC
- 相关概念: [[dom]], event-loop
Web 存储方案
定义
浏览器提供了多种客户端存储机制,适用于不同的数据规模、生命周期和使用场景。
方案对比
| localStorage | sessionStorage | Cookies | IndexedDB | |
|---|---|---|---|---|
| 生命周期 | 永久(手动清除) | 标签页关闭即清除 | 可设置过期时间 | 永久(手动清除) |
| 容量 | ~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
}, {})
Cookie 属性
path:cookie 的作用路径domain:cookie 的作用域max-age:存活秒数expires:过期时间点Secure:只通过 HTTPS 发送HttpOnly:JS 无法访问(防 XSS)SameSite:Strict/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=Strict或SameSite=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。
Related notes
- 前端基础 MOC
- [[dom]]
- event-loop