JavaScript高级程序设计笔记 25
第 25 章 客户端存储
学习目标:理解浏览器端存储方案的演进,掌握 cookie、Web Storage、IndexedDB 的特点、限制和适用场景。
1. 本章核心脉络
客户端存储解决的是“数据如何保存在用户浏览器中”的问题。早期主要依赖 cookie,后来出现了更适合前端应用的 sessionStorage、localStorage,再往后则有可存储大量结构化数据的 IndexedDB。
本章重点:
cookie:最早的客户端存储机制,会随 HTTP 请求发送。Web Storage:包含sessionStorage和localStorage,API 简单,适合键值存储。IndexedDB:浏览器内置的事务型数据库,适合大量结构化数据。
2. cookie
2.1 cookie 是什么
cookie 是保存在浏览器中的小块文本数据,最初用于在无状态 HTTP 协议中维护状态,比如登录态、用户偏好、追踪标识等。
特点:
- 按域名存储。
- 每次匹配请求都会自动带到服务器。
- 容量小。
- 字符串格式,不适合复杂数据。
- 涉及隐私和安全问题。
2.2 cookie 的限制
常见限制:
- 单个 cookie 大小通常约 4KB。
- 每个域名可保存 cookie 数量有限。
- cookie 会随请求自动发送,过多会增加网络开销。
- 不能存储敏感信息,除非有可靠加密和服务端保护。
2.3 cookie 的构成
常见属性:
| 属性 | 作用 |
|---|---|
name=value |
cookie 名和值 |
expires |
过期时间 |
max-age |
最大存活秒数 |
domain |
可访问 cookie 的域 |
path |
可访问 cookie 的路径 |
secure |
只在 HTTPS 下发送 |
HttpOnly |
禁止 JavaScript 读取 |
SameSite |
限制跨站请求携带 cookie |
示例:
Set-Cookie: token=abc; Path=/; Secure; HttpOnly; SameSite=Lax
2.4 JavaScript 操作 cookie
读取:
console.log(document.cookie);
写入:
document.cookie = "name=Alice; path=/; max-age=3600";
删除本质上是让它过期:
document.cookie = "name=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
注意:document.cookie 的接口很原始,读取时返回的是一个分号分隔的字符串,实际项目通常会封装工具函数。
2.5 子 cookie
子 cookie 是一种把多个键值编码进同一个 cookie 的技巧,用来绕开 cookie 数量限制。
示例形式:
data=name=Alice&theme=dark&lang=zh
缺点:
- 需要手动解析和序列化。
- 任意子项变更都要重写整个 cookie。
- 可维护性较差。
现代项目通常不再优先使用这种方式。
2.6 使用 cookie 的注意事项
- 不要保存密码、令牌明文等敏感数据。
- 能用
HttpOnly的认证 cookie 应尽量由服务端设置。 - HTTPS 场景下配合
Secure。 - 跨站安全场景下关注
SameSite。 - 不要把大量业务数据放进 cookie。
- 前端本地状态优先考虑 Web Storage 或 IndexedDB。
3. Web Storage
3.1 Storage 类型
Storage 提供键值对存储能力,键和值都会以字符串形式保存。
常用 API:
localStorage.setItem("theme", "dark");
localStorage.getItem("theme");
localStorage.removeItem("theme");
localStorage.clear();
也可以使用属性形式:
localStorage.theme = "dark";
console.log(localStorage.theme);
更推荐使用 getItem()、setItem(),语义更清晰。
3.2 sessionStorage
sessionStorage 的生命周期是页面会话。
特点:
- 数据只在当前标签页或窗口中有效。
- 页面刷新后仍存在。
- 关闭标签页后清除。
- 不会自动发送给服务器。
适合:
- 多步骤表单临时数据。
- 当前页面的临时筛选条件。
- 单标签页内的短期状态。
sessionStorage.setItem("draft", JSON.stringify({ title: "hello" }));
const draft = JSON.parse(sessionStorage.getItem("draft") || "{}");
3.3 localStorage
localStorage 的生命周期更长,除非手动清除,否则会长期保留。
适合:
- 用户主题偏好。
- 本地草稿。
- 非敏感配置。
- 小规模缓存。
localStorage.setItem("settings", JSON.stringify({
theme: "dark",
fontSize: 16,
}));
注意:
- 只能存字符串,复杂对象要
JSON.stringify()。 - 读取和写入是同步 API,频繁操作大数据可能阻塞主线程。
- 不适合保存敏感信息。
3.4 storage 事件
当同源页面中的 Web Storage 被其他页面修改时,会触发 storage 事件。
window.addEventListener("storage", (event) => {
console.log(event.key);
console.log(event.oldValue);
console.log(event.newValue);
console.log(event.url);
});
常见用途:
- 多标签页同步登录状态。
- 多窗口同步主题设置。
- 通知其他标签页缓存变化。
注意:通常修改发生的当前页面不会收到自己触发的 storage 事件。
3.5 Web Storage 限制
- 存储容量比 cookie 大,但仍有限。
- API 同步,不能滥用大数据读写。
- 用户或浏览器策略可能清理数据。
- 隐私模式下行为可能不同。
- 只能按字符串键值存储。
4. IndexedDB
4.1 IndexedDB 是什么
IndexedDB 是浏览器提供的低级客户端数据库,适合保存大量结构化数据。它是异步 API,支持事务、对象仓库、索引和游标。
适合:
- 离线应用数据。
- 大量列表缓存。
- 文件、Blob、结构化对象。
- 本地搜索和复杂查询。
不适合:
- 简单偏好设置。
- 需要服务端强一致性的核心数据。
4.2 打开数据库
const request = indexedDB.open("app", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore("users", { keyPath: "id" });
};
request.onsuccess = (event) => {
const db = event.target.result;
console.log(db);
};
request.onerror = () => {
console.error(request.error);
};
关键点:
indexedDB.open(name, version)打开数据库。- 首次创建或版本升级时触发
upgradeneeded。 - 对象仓库和索引通常在升级阶段创建。
4.3 对象存储
对象仓库类似表,用来保存对象。
db.createObjectStore("users", {
keyPath: "id",
});
主键可以是:
- 对象中的某个字段,如
id。 - 自动生成的键,如
{ autoIncrement: true }。 - 插入时手动传入的外部键。
4.4 事务
IndexedDB 的所有读写都在事务中完成。
const tx = db.transaction("users", "readwrite");
const store = tx.objectStore("users");
store.add({ id: 1, name: "Alice" });
tx.oncomplete = () => {
console.log("done");
};
事务模式:
readonly:只读。readwrite:读写。
事务可以保证一组操作要么完成,要么失败。
4.5 插入、读取、更新、删除
const store = db.transaction("users", "readwrite").objectStore("users");
store.add({ id: 1, name: "Alice" });
store.put({ id: 1, name: "Alice Updated" });
store.get(1);
store.delete(1);
区别:
add():新增,键已存在会失败。put():新增或更新。
4.6 游标查询
游标用于遍历对象仓库或索引。
const request = store.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log(cursor.key, cursor.value);
cursor.continue();
}
};
适合:
- 遍历大量数据。
- 分页。
- 条件过滤。
- 范围扫描。
4.7 键范围
IDBKeyRange 用于限定查询范围。
const range = IDBKeyRange.bound(10, 20);
store.openCursor(range);
常用方法:
only(value):只匹配指定值。lowerBound(value):下界。upperBound(value):上界。bound(lower, upper):上下界。
4.8 索引
索引用于按非主键字段查询。
store.createIndex("by_email", "email", {
unique: true,
});
查询:
const index = store.index("by_email");
index.get("a@example.com");
索引能提升查询能力,但会增加写入维护成本。
4.9 并发与版本问题
IndexedDB 有版本概念。升级数据库结构时,旧连接可能阻塞新版本打开。
实践建议:
- 数据库连接不用时及时关闭。
- 处理
versionchange事件。 - 升级逻辑保持可重复、可迁移。
- 结构变更集中放在
onupgradeneeded。
5. 方案选择
| 场景 | 推荐方案 |
|---|---|
| 服务端识别登录态 | cookie |
| 少量用户偏好 | localStorage |
| 当前标签页临时数据 | sessionStorage |
| 页面关闭时清除的数据 | sessionStorage |
| 大量结构化缓存 | IndexedDB |
| 离线应用数据 | IndexedDB |
| 需要随请求自动发送 | cookie |
6. 面试高频问题
6.1 cookie、localStorage、sessionStorage 的区别?
cookie 会随 HTTP 请求自动发送,容量小,常用于服务端会话。localStorage 长期保存,不随请求发送。sessionStorage 只在当前页面会话中保存,关闭标签页后清除。
6.2 localStorage 能存对象吗?
不能直接存对象。Web Storage 只存字符串,对象需要先 JSON.stringify(),读取后再 JSON.parse()。
6.3 为什么不建议把 token 放 localStorage?
因为 localStorage 可以被 JavaScript 读取,一旦页面存在 XSS 漏洞,攻击者可能直接窃取 token。更安全的认证方案通常会结合服务端设置的 HttpOnly、Secure、SameSite cookie。
6.4 IndexedDB 适合解决什么问题?
适合存储大量结构化数据和离线数据,支持异步操作、事务、索引、游标,比 Web Storage 更适合复杂客户端缓存。
6.5 storage 事件有什么用?
它可以让同源的多个标签页感知 Web Storage 的变化,常用于多标签页同步状态,比如退出登录、主题变更等。
7. 复习清单
8. 一句话总结
第 25 章的核心是客户端存储方案的分层选择:cookie 负责与服务端请求状态绑定,Web Storage 负责简单键值状态,IndexedDB 负责大量结构化和离线数据。

浙公网安备 33010602011771号