本地存储技术
随着前端应用复杂度的提升,本地存储技术从 Cookie 演进到 Web Storage 再到 IndexedDB,各有所长。本章系统梳理浏览器本地存储的技术原理、API 使用与选型策略。
存储技术演进
Cookie(1994)→ Web Storage(2008)→ IndexedDB(2011)→ Cache API(2015)
4KB 5-10MB 250MB+ 无限制| 技术 | 容量 | 生命周期 | 与服务端通信 | 数据类型 |
|---|---|---|---|---|
| Cookie | ≤ 4KB | 可设过期时间 | ✅ 自动携带 | 字符串 |
| localStorage | 5-10MB | 永久(手动删除) | ❌ | 字符串 |
| sessionStorage | 5-10MB | 会话级(Tab 关闭即清除) | ❌ | 字符串 |
| IndexedDB | 250MB+ | 永久(手动删除) | ❌ | 结构化数据 + 二进制 |
| Cache API | 无限制 | 永久(手动删除) | ❌ | HTTP 请求/响应 |
Cookie
设计初衷
Cookie 的本职工作是维持状态(Session 管理),而非存储数据。HTTP 协议是无状态的,Cookie 通过在请求中携带键值对来标识客户端身份。网站登录后"不用重复登录"的现象,正是 Cookie 存储和传输 token 的典型应用。
Token 存储方式
客户端存储 token 有两种方式:
服务端自动植入
服务端通过 Set-Cookie 响应头将 token 植入浏览器 Cookie:
Set-Cookie: token=abc123; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Lax之后同域下的所有请求,浏览器会自动携带 Cookie,前端无需关心 token 的存取。
前端手动存储
服务端将 token 通过响应体返回,前端手动存储到 localStorage 等:
import axios from 'axios'
const http = (params) => {
const instance = axios.create({ baseURL: 'https://example.com' })
const token = localStorage.getItem('token')
return instance({
url: '/api/data',
method: 'post',
data: params,
headers: { 'x-token': token } // 手动设置请求头
})
}两种方式对比:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 服务端植入 | 自动携带、HttpOnly 防 XSS | 仅浏览器环境可用 |
| 前端手动存储 | 跨环境可用(APP/小程序) | 需手动管理存取 |
性能劣势
| 问题 | 说明 |
|---|---|
| 容量限制 | 单个 Cookie 约 4KB,总量有限 |
| 性能开销 | 每次 HTTP 请求都会自动携带 Cookie,增大请求体积 |
| 安全风险 | 若被 XSS 窃取,会导致身份泄露(需配合 HttpOnly) |
| 传输浪费 | 无关请求也携带 Cookie,造成带宽浪费 |
SameSite 属性
SameSite 用于控制 Cookie 跨站携带策略,防止 CSRF:
Strict:严格同站,跨站不携带Lax:默认值,部分跨站请求(如导航)携带None:允许跨站携带,必须配合Secure
Cookie API
原生 API 操作不够便捷:
// 设置
document.cookie = 'name=juejin; path=/; max-age=3600; SameSite=Lax; Secure'
// 读取(返回所有 Cookie 字符串,需手动解析)
console.log(document.cookie)
// 删除(设置过期时间)
document.cookie = 'name=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'推荐使用 js-cookie 封装:
import Cookies from 'js-cookie'
Cookies.set('name', 'juejin', { domain: 'example.com' })
Cookies.get('name') // 'juejin'
Cookies.remove('name')Web Storage
Web Storage 是 HTML5 提供的浏览器端数据存储机制,分为 localStorage 和 sessionStorage,API 完全一致。
localStorage vs sessionStorage
| 维度 | localStorage | sessionStorage |
|---|---|---|
| 生命周期 | 永久(除非手动删除) | 会话级别(标签页关闭即清除) |
| 作用域 | 同源策略(同协议+域名+端口共享) | 同源 + 仅限同一窗口/标签页 |
| 容量 | 5-10 MB | 5-10 MB |
| 与服务端通信 | 不通信 | 不通信 |
| 跨 Tab 共享 | ✅ | ❌ |
核心 API
// 存储数据
localStorage.setItem('user_name', 'xiuyan')
localStorage.setItem('user_info', JSON.stringify({ id: 1, role: 'admin' }))
// 读取数据
const name = localStorage.getItem('user_name')
const info = JSON.parse(localStorage.getItem('user_info'))
// 删除某条数据
localStorage.removeItem('user_name')
// 清空所有数据
localStorage.clear()
// 获取存储数量
console.log(localStorage.length)
// 按 index 获取 key
console.log(localStorage.key(0))数据类型陷阱
Web Storage 存储的数据都会被转为字符串:
localStorage.setItem('age', 18)
localStorage.getItem('age') // '18'(字符串,非数字)
// 存储对象不序列化 → [object Object]
localStorage.setItem('obj', { a: 1 })
localStorage.getItem('obj') // '[object Object]'
// 正确做法:序列化存储
localStorage.setItem('obj', JSON.stringify({ a: 1 }))
JSON.parse(localStorage.getItem('obj')) // { a: 1 }封装方案
封装 localStorage 可以增加过期时间管理和自动序列化/反序列化:
const storage = {
set(key, value, duration) {
const data = {
value,
expiryTime: !duration || isNaN(duration)
? 0
: Date.now() + parseInt(duration)
}
localStorage.setItem(key, JSON.stringify(data))
},
get(key) {
const data = JSON.parse(localStorage.getItem(key))
if (data && data.expire) {
// 过期则删除并返回 null
if (Date.now() > data.expire) {
localStorage.removeItem(key)
return null
}
return data.value
}
return data
}
}
// 使用
storage.set('userinfo', { name: 'juejin', age: 18 }, 3600000) // 1小时过期
storage.get('userinfo') // { name: 'juejin', age: 18 }也可使用 npm 包 web-storage-cache。
监听变化
同源下的其他标签页/窗口可以监听到 Storage 变化(当前页面自身修改不会触发):
window.addEventListener('storage', (event) => {
console.log(`Key: ${event.key}`)
console.log(`Old Value: ${event.oldValue}`)
console.log(`New Value: ${event.newValue}`)
console.log(`URL: ${event.url}`)
console.log(`Storage: ${event.storageArea}`)
})应用场景
| 存储类型 | 适用场景 |
|---|---|
| localStorage | 用户偏好设置、主题配置、不常更新的静态数据、Base64 图片缓存 |
| sessionStorage | 表单临时数据、页面状态恢复、浏览足迹、分页信息、单次会话的认证态 |
IndexedDB
IndexedDB 是浏览器内置的非关系型数据库(NoSQL),支持存储结构化数据和二进制大文件,是浏览器端最强大的存储方案。
核心特性
| 特性 | 说明 |
|---|---|
| 存储容量 | 理论无上限(通常 > 250MB) |
| 数据类型 | 字符串、对象、ArrayBuffer、Blob 等 |
| 支持操作 | 增删改查、索引、事务、游标 |
| 异步操作 | 基于 DOM 事件或 Promise |
| 同源限制 | 遵循同源策略 |
数据库结构
IndexedDB
├── Database(数据库)
│ └── Object Store(对象仓库,类似"表")
│ ├── Key(主键,唯一标识记录)
│ ├── Value(存储的数据,可以是任意结构化数据)
│ └── Index(索引,加速查询)
└── Transaction(事务,保证操作的原子性)基础使用
打开/创建数据库
const request = indexedDB.open('myDatabase', 1)
request.onerror = () => console.error('IndexedDB 打开失败')
// 版本变化时触发(新建或版本号升级)
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true
})
// 创建索引(索引名、索引属性、配置)
store.createIndex('nameIndex', 'name', { unique: false })
store.createIndex('emailIndex', 'email', { unique: true })
}
}
request.onsuccess = (event) => {
const db = event.target.result
console.log('数据库打开成功')
}写入数据
function addData(db, data) {
const transaction = db.transaction(['users'], 'readwrite')
const store = transaction.objectStore('users')
const request = store.add(data)
request.onsuccess = () => console.log('写入成功')
request.onerror = () => console.error('写入失败')
transaction.oncomplete = () => console.log('事务完成')
}
addData(db, { name: '张三', email: 'zhangsan@example.com', age: 25 })读取数据
// 按主键查询
function getData(db, id) {
const transaction = db.transaction(['users'], 'readonly')
const store = transaction.objectStore('users')
const request = store.get(id)
request.onsuccess = () => {
console.log('查询结果:', request.result)
}
}
// 按索引查询
function getByIndex(db, name) {
const transaction = db.transaction(['users'], 'readonly')
const store = transaction.objectStore('users')
const index = store.index('nameIndex')
const request = index.get(name)
request.onsuccess = () => {
console.log('索引查询结果:', request.result)
}
}更新与删除
// 更新(put 方法:存在则更新,不存在则新增)
function updateData(db, data) {
const transaction = db.transaction(['users'], 'readwrite')
const store = transaction.objectStore('users')
store.put(data)
}
// 删除
function deleteData(db, id) {
const transaction = db.transaction(['users'], 'readwrite')
const store = transaction.objectStore('users')
store.delete(id)
}使用游标遍历
function cursorGetAll(db) {
const transaction = db.transaction(['users'], 'readonly')
const store = transaction.objectStore('users')
const request = store.openCursor()
const results = []
request.onsuccess = (event) => {
const cursor = event.target.result
if (cursor) {
results.push(cursor.value)
cursor.continue() // 移动到下一条
} else {
console.log('所有数据:', results)
}
}
}使用 idb 库简化操作
原生 IndexedDB API 基于事件回调,使用不便。推荐使用 idb 库,将 API 转为 Promise 风格:
import { openDB } from 'idb'
const db = await openDB('myDatabase', 1, {
upgrade(db) {
db.createObjectStore('users', { keyPath: 'id' })
}
})
// 写入
await db.add('users', { id: 1, name: '张三', email: 'zhangsan@example.com' })
// 读取
const user = await db.get('users', 1)
// 更新
await db.put('users', { id: 1, name: '李四', email: 'lisi@example.com' })
// 删除
await db.delete('users', 1)
// 遍历
const allUsers = await db.getAll('users')应用场景
| 场景 | 说明 |
|---|---|
| 离线应用 | PWA 中存储离线所需的大量结构化数据 |
| 大文件管理 | 存储 Blob 数据(图片、视频、文档等) |
| 复杂状态 | 需要索引和查询的复杂数据模型 |
| 缓存层 | 作为 Service Worker 的持久化缓存后端 |
| 草稿保存 | 富文本编辑器内容自动保存 |
浏览器兼容性
IndexedDB 在现代浏览器中支持良好,但部分旧版浏览器存在兼容问题。使用时应遵循渐进增强原则,先检测支持情况:
if (!('indexedDB' in window)) {
console.log('浏览器不支持 IndexedDB')
// 降级到 Web Storage
}存储方案选型
决策流程
需要存储什么?
├── 少量文本(< 4KB)+ 服务端需读取 → Cookie
│ └── 需防 XSS → HttpOnly + Secure + SameSite
├── 简单键值对(< 10MB)+ 不需要服务端 → Web Storage
│ ├── 需要跨会话保持 → localStorage
│ └── 仅当前会话 → sessionStorage
├── 结构化数据 / 大文件 / 需要索引查询 → IndexedDB
└── HTTP 缓存替代方案 → Cache API(配合 Service Worker)选型对比
| 维度 | Cookie | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|---|
| 容量 | ≤ 4KB | 5-10MB | 5-10MB | 250MB+ |
| 生命周期 | 可配置 | 永久 | 会话级 | 永久 |
| 数据类型 | 字符串 | 字符串 | 字符串 | 结构化 + 二进制 |
| 网络传输 | 自动携带 | 不传输 | 不传输 | 不传输 |
| 查询能力 | 无 | key 查询 | key 查询 | 索引 + 游标 |
| 事务支持 | ❌ | ❌ | ❌ | ✅ |
| 异步 API | ❌ | ❌ | ❌ | ✅ |
| 跨 Tab | ✅(同源) | ✅(同源) | ❌ | ✅(同源) |
最佳实践
- Cookie:仅用于身份认证和少量必须传递给服务端的数据,始终设置
HttpOnly、Secure、SameSite - localStorage:适合持久化的用户偏好、配置信息;注意序列化/反序列化和容量控制
- sessionStorage:适合临时性数据,如表单暂存、页面状态恢复
- IndexedDB:适合大数据量、结构化存储场景;推荐使用
idb库简化操作 - Cache API:配合 Service Worker 实现 HTTP 缓存,适合离线应用
- 敏感数据:任何本地存储都不应存放密码、密钥等敏感信息
- 容量管理:定期清理过期数据,避免占用过多存储空间