浏览器存储方案完全指南
前端开发中,将数据持久化到浏览器是一个高频需求。但浏览器提供了多种存储方案,面对 Cookie、localStorage、sessionStorage、IndexedDB、Cache API,如何选择?本文做一次系统梳理。
快速对比
| 方案 | 容量 | 生命周期 | 可以被服务端读取 | 同步/异步 | 适合存储 |
|---|---|---|---|---|---|
| Cookie | 4KB | 可设置过期时间 | ✅ 每次请求自动携带 | 同步 | 身份凭证、少量数据 |
| localStorage | ~5MB | 永久(手动清除) | ❌ | 同步 | 用户偏好、持久化数据 |
| sessionStorage | ~5MB | 标签页关闭即清除 | ❌ | 同步 | 临时表单数据、页面状态 |
| IndexedDB | ~250MB+ | 永久(手动清除) | ❌ | 异步 | 大量结构化数据、文件 |
| Cache API | 受磁盘限制 | 永久(手动清除) | ❌ | 异步 | HTTP 响应缓存(PWA) |
Cookie
Cookie 是最古老的浏览器存储机制,核心特点是每次 HTTP 请求都会自动携带,因此主要用于身份认证。
基础操作
// 写入
document.cookie = "username=Alice; max-age=86400; path=/"; // max-age 单位秒
// 读取(返回所有 cookie 拼成的字符串,需要自己解析)
document.cookie; // "username=Alice; theme=dark"
// 封装成工具函数更好用
const Cookie = {
set(key, value, days = 7) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${key}=${encodeURIComponent(
value
)}; expires=${expires}; path=/`;
},
get(key) {
const match = document.cookie.match(new RegExp(`(?:^|; )${key}=([^;]*)`));
return match ? decodeURIComponent(match[1]) : null;
},
remove(key) {
document.cookie = `${key}=; max-age=0; path=/`;
},
};
Cookie.set("theme", "dark");
Cookie.get("theme"); // "dark"
Cookie.remove("theme");
安全属性
// HttpOnly:禁止 JS 读取(只能服务端设置),防止 XSS 盗取
// Set-Cookie: session=xxx; HttpOnly
// Secure:只在 HTTPS 下传输
// Set-Cookie: session=xxx; Secure
// SameSite:防止 CSRF 攻击
// SameSite=Strict:完全禁止跨站携带
// SameSite=Lax:导航跳转时携带,第三方请求不携带(推荐默认值)
// SameSite=None:始终携带(需配合 Secure)
使用建议:Cookie 应专注于存储服务端需要读取的数据(如 session token),前端偏好数据用 localStorage 更合适。
localStorage
localStorage 是前端最常用的持久化存储,关闭浏览器后数据依然存在。
基础操作
// 写入(值必须是字符串,对象需序列化)
localStorage.setItem("user", JSON.stringify({ name: "Alice", role: "admin" }));
// 读取
const user = JSON.parse(localStorage.getItem("user") ?? "null");
// 删除
localStorage.removeItem("user");
// 清空当前域名下所有数据
localStorage.clear();
// 封装:支持任意类型,附带过期时间
const storage = {
set(key, value, ttl) {
const data = { value, expires: ttl ? Date.now() + ttl : null };
localStorage.setItem(key, JSON.stringify(data));
},
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const { value, expires } = JSON.parse(raw);
if (expires && Date.now() > expires) {
localStorage.removeItem(key);
return null;
}
return value;
},
remove(key) {
localStorage.removeItem(key);
},
};
// 存储 token,1小时过期
storage.set("token", "eyJhbG...", 60 * 60 * 1000);
storage.get("token"); // 1小时内有效,过期自动返回 null
跨标签页通信
localStorage 有一个鲜为人知的功能:在同一域名的其他标签页修改数据时,当前标签页会触发 storage 事件。
// 标签页 A 修改数据
localStorage.setItem("count", "42");
// 标签页 B 监听变化
window.addEventListener("storage", (event) => {
if (event.key === "count") {
console.log(`count 从 ${event.oldValue} 变为 ${event.newValue}`);
// 可以用来实现跨标签同步登录状态、主题等
}
});
注意:当前标签页修改 localStorage 不会触发自身的
storage事件,只触发其他标签页的。
sessionStorage
sessionStorage 与 localStorage API 完全一致,区别在于生命周期:关闭标签页或浏览器后数据就消失。
适用场景:
- 多步骤表单的中间状态(防止刷新丢失)
- 单次会话的临时数据
- 防止表单重复提交
// 保存多步骤表单进度
function saveStepData(step, data) {
sessionStorage.setItem(`form_step_${step}`, JSON.stringify(data));
}
function getStepData(step) {
return JSON.parse(sessionStorage.getItem(`form_step_${step}`) ?? "null");
}
function clearForm() {
// 表单完成后清除所有步骤数据
for (let i = 1; i <= 3; i++) {
sessionStorage.removeItem(`form_step_${i}`);
}
}
IndexedDB
IndexedDB 是一个完整的客户端数据库,支持索引、事务、游标,适合存储大量结构化数据。原生 API 比较繁琐,推荐用封装库。
原生 API 示意
// 1. 打开数据库(异步)
const request = indexedDB.open("MyDB", 1);
// 2. 数据库结构升级(首次创建或版本更新时触发)
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建 "products" 对象仓库,主键为 id
const store = db.createObjectStore("products", {
keyPath: "id",
autoIncrement: true,
});
// 创建索引,加速按 name 查询
store.createIndex("name", "name", { unique: false });
};
// 3. 读写数据
request.onsuccess = (event) => {
const db = event.target.result;
// 写入
const tx = db.transaction("products", "readwrite");
tx.objectStore("products").add({ name: "键盘", price: 399 });
// 读取
const getTx = db.transaction("products", "readonly");
getTx.objectStore("products").get(1).onsuccess = (e) => {
console.log(e.target.result);
};
};
推荐使用 idb 库
idb 是 Jake Archibald 写的 IndexedDB Promise 封装,极大简化了 API:
import { openDB } from "idb";
const db = await openDB("MyDB", 1, {
upgrade(db) {
const store = db.createObjectStore("products", {
keyPath: "id",
autoIncrement: true,
});
store.createIndex("name", "name");
},
});
// 写入
await db.add("products", { name: "键盘", price: 399 });
await db.put("products", { id: 1, name: "机械键盘", price: 599 }); // 更新
// 读取
const product = await db.get("products", 1);
const all = await db.getAll("products");
// 按索引查询
const byName = await db.getAllFromIndex("products", "name", "键盘");
// 删除
await db.delete("products", 1);
适用场景:
- 离线应用的本地数据库
- 缓存大量 API 数据(避免重复请求)
- 存储用户上传的文件或 Blob 对象
- 需要复杂查询的场景
Cache API
Cache API 专为 Service Worker 设计,用于缓存 HTTP 请求和响应,是 PWA(渐进式 Web 应用)的核心技术。
// Service Worker 中缓存静态资源
const CACHE_NAME = "my-app-v1";
const ASSETS = ["/", "/index.html", "/app.js", "/style.css"];
// 安装时预缓存
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
);
});
// 请求时:缓存优先策略
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
// 有缓存直接返回,同时后台更新
if (cached) {
fetch(event.request).then((response) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, response);
});
});
return cached;
}
// 没有缓存则请求网络
return fetch(event.request);
})
);
});
如何选择?
根据实际需求选择存储方案:
需要服务端读取?
├── 是 → Cookie(HttpOnly + Secure + SameSite)
└── 否 → 前端自己管理
│
├── 大量结构化数据 / 需要查询 / 要存文件?
│ └── IndexedDB(用 idb 库)
│
├── 缓存 HTTP 响应 / PWA 离线?
│ └── Cache API
│
├── 关闭标签页就失效?
│ └── sessionStorage
│
└── 其他(用户偏好、token、持久化简单数据)
└── localStorage(注意 5MB 上限)
常见注意事项
-
localStorage/sessionStorage 是同步的:大量读写会阻塞主线程,避免在循环中频繁操作
-
存储配额不能假定:虽然通常有 5MB,但用户可以清除,代码要做好容错处理
-
不要存储敏感信息:localStorage 可被同域 JS 读取,XSS 攻击可以窃取数据,token 最好存 HttpOnly Cookie
-
私有浏览/无痕模式:存储可能有限制,Safari 下 localStorage 在私有模式下写入会抛出异常
// 安全的 localStorage 写入(兼容异常情况)
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
} catch (e) {
// 满了或私有模式被禁用
console.warn("localStorage 写入失败:", e);
}
}