浏览器存储
在前端开发中,数据存储是绑定用户状态、提升页面性能、实现离线功能的关键能力。浏览器提供了多种存储方案——从最早的 Cookie 到现代的 IndexedDB 和 Cache API,每种方案都有其适用场景和局限性。掌握它们的原理与差异,不仅是日常开发的基本功,也是前端面试的高频考点。
存储方案概览
浏览器端有以下几种主要的存储机制:
| 存储方案 | 容量上限 | 生命周期 | 是否随请求发送 | 数据格式 |
|---|---|---|---|---|
| Cookie | ~4KB | 可设过期时间 | ✅ | 字符串键值对 |
| localStorage | ~5-10MB | 永久(手动清除) | ❌ | 字符串键值对 |
| sessionStorage | ~5-10MB | 当前会话(标签页关闭) | ❌ | 字符串键值对 |
| IndexedDB | 数百 MB 甚至更多 | 永久(手动清除) | ❌ | 结构化数据(对象、二进制) |
| Cache API | 取决于磁盘空间 | 永久(手动清除) | ❌ | HTTP 请求/响应对 |
把浏览器存储想象成你家里的收纳空间:
- Cookie 像随身携带的名片夹,容量很小,但每次出门(请求)都会带上
- localStorage 像家里的柜子,东西放进去不会自动消失
- sessionStorage 像酒店的临时储物柜,退房(关闭标签页)就清空
- IndexedDB 像一个私人仓库,能存大量各种类型的物品
- Cache API 像快递驿站的缓存架,专门存取网络请求的"包裹"
Cookie
什么是 Cookie?
Cookie 是浏览器存储在客户端的一小段文本数据,最初设计用于在 HTTP 请求中携带状态信息。因为 HTTP 协议本身是无状态的,服务器无法区分两次请求是否来自同一用户,Cookie 就是为了解决这个问题而诞生的。
Cookie 的属性
每个 Cookie 除了 name=value 之外,还有以下重要属性:
| 属性 | 说明 | 示例 |
|---|---|---|
Domain | Cookie 所属域名 | Domain=example.com |
Path | Cookie 生效的路径 | Path=/api |
Expires / Max-Age | 过期时间 | Max-Age=3600(1 小时) |
HttpOnly | 禁止 JS 访问(防 XSS) | HttpOnly |
Secure | 仅 HTTPS 下发送 | Secure |
SameSite | 控制跨站发送行为(防 CSRF) | SameSite=Strict |
Cookie 的操作
服务端设置 Cookie(通过响应头):
Set-Cookie: token=abc123; Max-Age=86400; HttpOnly; Secure; SameSite=Strict; Path=/
客户端读写 Cookie(通过 JS):
// ✅ 写入 Cookie
document.cookie = 'username=terry; max-age=3600; path=/';
// ✅ 读取所有 Cookie(返回一个字符串)
console.log(document.cookie);
// 输出: "username=terry; theme=dark"
// ✅ 删除 Cookie(设置过期时间为过去)
document.cookie = 'username=; max-age=0; path=/';
document.cookie 的 API 非常原始——读取时返回所有 Cookie 拼成的字符串,写入时一次只能设置一个 Cookie。实际项目中推荐使用 js-cookie 等工具库来简化操作。
Cookie 的封装工具函数
为了方便操作,我们可以封装一些工具函数:
// 设置 Cookie
function setCookie(name, value, days) {
const maxAge = days * 24 * 60 * 60;
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; max-age=${maxAge}; path=/`;
}
// 获取 Cookie
function getCookie(name) {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [key, val] = cookie.split('=');
if (decodeURIComponent(key) === name) {
return decodeURIComponent(val);
}
}
return null;
}
// 删除 Cookie
function deleteCookie(name) {
document.cookie = `${encodeURIComponent(name)}=; max-age=0; path=/`;
}
// 使用示例
setCookie('theme', 'dark', 30); // 设置 30 天有效的主题偏好
console.log(getCookie('theme')); // "dark"
deleteCookie('theme'); // 删除
SameSite 属性详解
SameSite 是防御 CSRF 攻击的重要属性,有三个取值:
| 场景 | Strict | Lax | None |
|---|---|---|---|
| 同站正常请求 | ✅ 发送 | ✅ 发送 | ✅ 发送 |
| 从外部链接点进来(顶级导航) | ❌ 不发送 | ✅ 发送 | ✅ 发送 |
| 第三方 iframe 中 | ❌ 不发送 | ❌ 不发送 | ✅ 发送 |
| 第三方发起的 AJAX/Fetch | ❌ 不发送 | ❌ 不发送 | ✅ 发送 |
| 表单 POST 提交(跨站) | ❌ 不发送 | ❌ 不发送 | ✅ 发送 |
从 Chrome 80 开始,SameSite 默认值已从 None 改为 Lax。如果需要跨站场景(如第三方登录、支付回调),必须显式设置 SameSite=None; Secure。
Cookie 的局限性
- 容量极小:每个域名下的 Cookie 总量约 4KB,不适合存大量数据
- 性能损耗:每次 HTTP 请求都会自动携带同域 Cookie,增加带宽消耗
- API 简陋:原生
document.cookie接口不好用 - 安全风险:若未正确配置
HttpOnly、Secure、SameSite,易遭 XSS/CSRF 攻击
Web Storage(localStorage 与 sessionStorage)
基本介绍
Web Storage 是 HTML5 引入的本地存储方案,包括 localStorage 和 sessionStorage。它们提供了比 Cookie 更大的存储空间和更友好的 API,且不会随 HTTP 请求发送。
localStorage 与 sessionStorage 的区别
| 特性 | localStorage | sessionStorage |
|---|---|---|
| 数据生命周期 | 永久存储 | 当前会话(标签页级) |
| 跨标签页共享 | ✅ 同源共享 | ❌ 每个标签页独立 |
| 存储容量 | ~5-10MB | ~5-10MB |
| 数据格式 | 字符串 | 字符串 |
| 随请求发送 | ❌ | ❌ |
sessionStorage 的会话是标签页级别的:
- 在同一标签页中跳转页面,数据保留
- 通过
window.open或<a target="_blank">打开的新标签页,会复制一份当前标签页的sessionStorage(但之后各自独立) - 关闭标签页后数据丢失
- 浏览器的"恢复标签页"功能可能会恢复
sessionStorage
API 操作
localStorage 和 sessionStorage 拥有完全相同的 API:
// ✅ 存储数据
localStorage.setItem('username', 'terry');
// ✅ 读取数据
const name = localStorage.getItem('username'); // "terry"
// ✅ 删除某条数据
localStorage.removeItem('username');
// ✅ 清空所有数据
localStorage.clear();
// ✅ 获取存储条目数量
console.log(localStorage.length); // 0
// ✅ 按索引获取 key
const firstKey = localStorage.key(0);
存储复杂数据
Web Storage 只能存储字符串。对于对象、数组等复杂数据类型,需要进行序列化/反序列化:
// ❌ 错误:直接存对象,会变成 "[object Object]"
localStorage.setItem('user', { name: 'terry', age: 25 });
console.log(localStorage.getItem('user')); // "[object Object]"
// ✅ 正确:使用 JSON 序列化
const user = { name: 'terry', age: 25 };
localStorage.setItem('user', JSON.stringify(user));
const stored = JSON.parse(localStorage.getItem('user'));
console.log(stored.name); // "terry"
封装带过期时间的 localStorage
原生 localStorage 不支持设置过期时间,但我们可以自行封装:
const storage = {
/**
* 存储数据,支持可选的过期时间
* @param {string} key - 键名
* @param {*} value - 值(任意类型)
* @param {number} [expireMs] - 过期时间(毫秒),不传则永不过期
*/
set(key, value, expireMs) {
const data = {
value,
timestamp: Date.now(),
expire: expireMs || null,
};
localStorage.setItem(key, JSON.stringify(data));
},
/**
* 读取数据,已过期则自动清除并返回 null
*/
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const data = JSON.parse(raw);
// 检查是否已过期
if (data.expire && Date.now() - data.timestamp > data.expire) {
localStorage.removeItem(key);
return null;
}
return data.value;
},
remove(key) {
localStorage.removeItem(key);
},
};
// 使用示例
storage.set('token', 'abc123', 1000 * 60 * 60); // 1 小时后过期
storage.set('theme', 'dark'); // 永不过期
console.log(storage.get('token')); // "abc123"(1 小时内有效)
console.log(storage.get('theme')); // "dark"
监听存储变化(storage 事件)
当同源的其他标签页修改了 localStorage,当前标签页可以通过 storage 事件来监听:
// 在标签页 A 中监听
window.addEventListener('storage', (event) => {
console.log('变化的 key:', event.key);
console.log('旧值:', event.oldValue);
console.log('新值:', event.newValue);
console.log('来源页面:', event.url);
});
// 在标签页 B 中修改
localStorage.setItem('theme', 'light');
// → 标签页 A 会触发 storage 事件
storage 事件是实现跨标签页通信的简单方式之一。注意:当前标签页自身的修改不会触发 storage 事件,只有其他同源标签页的修改才会触发。
Web Storage 的注意事项
- 同步阻塞:
localStorage的读写操作是同步的,大量数据读写可能阻塞主线程 - 仅支持字符串:存取复杂数据必须 JSON 序列化/反序列化
- 无法被 Web Worker 访问:Web Storage 只能在主线程使用
- 隐私模式限制:部分浏览器在隐私模式下可能限制 Web Storage 的容量或直接抛异常
IndexedDB
什么是 IndexedDB?
IndexedDB 是浏览器提供的一个低层级的异步 NoSQL 数据库,适合存储大量结构化数据,包括文件和二进制对象(Blob)。它的容量远超 Web Storage,是客户端存储复杂数据的首选方案。
IndexedDB 的特点
| 特点 | 说明 |
|---|---|
| 异步 API | 基于事件/Promise,不会阻塞主线程 |
| 支持事务 | 数据操作必须在事务中完成,保证数据一致性 |
| 同源策略 | 数据库只能在同源页面中访问 |
| 存储容量大 | 通常为磁盘可用空间的一定比例(数百 MB 甚至更多) |
| 支持索引 | 可以为字段创建索引,加速查询 |
| 数据类型丰富 | 支持对象、数组、Blob、ArrayBuffer 等结构化克隆数据 |
基础操作示例
IndexedDB 的原生 API 基于事件回调,相对繁琐。下面是一个完整的 CRUD 示例:
// 1️⃣ 打开(或创建)数据库
const request = indexedDB.open('MyDatabase', 1);
// 数据库版本升级时创建对象仓库
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建对象仓库,以 id 为主键,并自增
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;
// 2️⃣ 新增数据
function addUser(user) {
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
const req = store.add(user);
req.onsuccess = () => console.log('添加成功,id:', req.result);
req.onerror = () => console.error('添加失败:', req.error);
}
// 3️⃣ 查询数据
function getUser(id) {
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const req = store.get(id);
req.onsuccess = () => console.log('查询结果:', req.result);
}
// 4️⃣ 更新数据
function updateUser(user) {
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.put(user); // put 会根据主键更新或插入
}
// 5️⃣ 删除数据
function deleteUser(id) {
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.delete(id);
}
// 使用示例
addUser({ name: 'terry', email: 'terry@example.com', age: 25 });
addUser({ name: 'alice', email: 'alice@example.com', age: 22 });
};
request.onerror = (event) => {
console.error('数据库打开失败:', event.target.error);
};
使用 Promise 封装 IndexedDB
原生 API 基于事件回调,代码不够优雅。我们可以用 Promise 封装:
function openDB(dbName, version, onUpgrade) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, version);
request.onupgradeneeded = (e) => onUpgrade(e.target.result);
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
}
function dbOperation(db, storeName, mode, callback) {
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, mode);
const store = tx.objectStore(storeName);
const request = callback(store);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 使用示例
async function demo() {
const db = await openDB('MyDB', 1, (db) => {
db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
});
// 新增
await dbOperation(db, 'notes', 'readwrite', (store) =>
store.add({ title: '学习笔记', content: 'IndexedDB 很强大' })
);
// 查询
const note = await dbOperation(db, 'notes', 'readonly', (store) =>
store.get(1)
);
console.log(note); // { id: 1, title: '学习笔记', content: 'IndexedDB 很强大' }
}
实际项目中,更推荐使用成熟的封装库来简化 IndexedDB 操作:
- idb:轻量级 Promise 封装,API 优雅
- Dexie.js:功能强大的 IndexedDB 封装库,支持链式查询
- localForage:统一的异步存储 API,自动降级(IndexedDB → WebSQL → localStorage)
使用索引进行高级查询
// 假设已有数据库和 users 仓库
function getUsersByName(db, name) {
return new Promise((resolve, reject) => {
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const index = store.index('nameIndex');
// 精确匹配
const req = index.getAll(name);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
function getUsersInAgeRange(db, min, max) {
return new Promise((resolve, reject) => {
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const index = store.index('ageIndex');
// 范围查询
const range = IDBKeyRange.bound(min, max);
const req = index.getAll(range);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
Cache API
什么是 Cache API?
Cache API 是配合 Service Worker 使用的缓存接口,专门用于缓存网络请求与响应。它是实现 PWA(渐进式 Web 应用) 离线功能的核心技术。
基本操作
// 打开一个缓存空间
const cache = await caches.open('my-cache-v1');
// ✅ 缓存一个请求-响应对
await cache.put('/api/data', new Response(JSON.stringify({ hello: 'world' })));
// ✅ 缓存一组 URL(自动发起请求并缓存)
await cache.addAll(['/index.html', '/styles.css', '/app.js']);
// ✅ 从缓存中查找
const response = await cache.match('/api/data');
if (response) {
const data = await response.json();
console.log(data); // { hello: "world" }
}
// ✅ 删除缓存条目
await cache.delete('/api/data');
// ✅ 删除整个缓存空间
await caches.delete('my-cache-v1');
常见缓存策略
在 Service Worker 中,有几种经典的缓存策略:
缓存优先策略示例(Service Worker 中):
// sw.js - Service Worker 文件
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
// 缓存命中,直接返回
if (cachedResponse) {
return cachedResponse;
}
// 缓存未命中,从网络获取,并存入缓存
return fetch(event.request).then((networkResponse) => {
// 克隆响应(因为响应只能消费一次)
const cloned = networkResponse.clone();
caches.open('my-cache-v1').then((cache) => {
cache.put(event.request, cloned);
});
return networkResponse;
});
})
);
});
Stale While Revalidate 策略示例:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('my-cache-v1').then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
// 不管缓存有没有,都发网络请求更新缓存
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
// 如果有缓存,先返回缓存;否则等待网络
return cachedResponse || fetchPromise;
});
})
);
});
存储方案深度对比
使用场景选择
详细对比表
| 对比维度 | Cookie | localStorage | sessionStorage | IndexedDB | Cache API |
|---|---|---|---|---|---|
| 容量 | ~4KB | ~5-10MB | ~5-10MB | 数百 MB+ | 取决于磁盘 |
| API 类型 | 同步(字符串) | 同步 | 同步 | 异步(事件/Promise) | 异步(Promise) |
| 数据格式 | 字符串 | 字符串 | 字符串 | 结构化数据 | Request/Response |
| 生命周期 | 可配置过期 | 永久 | 标签页会话 | 永久 | 永久 |
| 随请求发送 | ✅ | ❌ | ❌ | ❌ | ❌ |
| Web Worker 可用 | ❌ | ❌ | ❌ | ✅ | ✅ |
| 事务支持 | ❌ | ❌ | ❌ | ✅ | ❌ |
| 典型场景 | 认证令牌 | 用户偏好 | 临时表单 | 离线数据库 | PWA 离线缓存 |
存储安全与最佳实践
安全风险与防御
最佳实践
1. 敏感数据不要存在客户端
// ❌ 差:在 localStorage 中存储密码或密钥
localStorage.setItem('password', 'mySecret123');
localStorage.setItem('apiKey', 'sk-xxxxxxxxxxxx');
// ✅ 好:敏感令牌使用 HttpOnly Cookie,客户端无法通过 JS 读取
// 由服务端通过 Set-Cookie 响应头设置
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict
2. 存储数据做好容错处理
// ❌ 差:不做任何错误处理
const user = JSON.parse(localStorage.getItem('user'));
// ✅ 好:完善的容错处理
function safeGetJSON(key) {
try {
const raw = localStorage.getItem(key);
return raw ? JSON.parse(raw) : null;
} catch (e) {
console.warn(`解析 ${key} 失败,已清除损坏数据`);
localStorage.removeItem(key);
return null;
}
}
3. 注意存储配额
// ✅ 使用 StorageManager API 查询存储配额
async function checkStorageQuota() {
if (navigator.storage && navigator.storage.estimate) {
const { usage, quota } = await navigator.storage.estimate();
console.log(`已使用: ${(usage / 1024 / 1024).toFixed(2)} MB`);
console.log(`总配额: ${(quota / 1024 / 1024).toFixed(2)} MB`);
console.log(`使用率: ${((usage / quota) * 100).toFixed(2)}%`);
}
}
4. 存储数据版本管理
const STORAGE_VERSION = 2;
function migrateStorage() {
const currentVersion = Number(localStorage.getItem('_version')) || 0;
if (currentVersion < 1) {
// v0 → v1: 旧格式迁移
const oldTheme = localStorage.getItem('theme');
if (oldTheme) {
localStorage.setItem('settings', JSON.stringify({ theme: oldTheme }));
localStorage.removeItem('theme');
}
}
if (currentVersion < 2) {
// v1 → v2: 添加新字段
const settings = JSON.parse(localStorage.getItem('settings') || '{}');
settings.fontSize = settings.fontSize || 14;
localStorage.setItem('settings', JSON.stringify(settings));
}
localStorage.setItem('_version', String(STORAGE_VERSION));
}
// 应用启动时执行迁移
migrateStorage();
面试高频问答
Q1:Cookie、localStorage、sessionStorage 三者有什么区别?
答: 主要区别体现在以下几个方面:
- 容量:Cookie 约 4KB;localStorage 和 sessionStorage 约 5-10MB
- 生命周期:Cookie 可设过期时间;localStorage 永久有效;sessionStorage 随标签页关闭而清除
- 网络传输:Cookie 每次 HTTP 请求都会自动携带;Web Storage 不参与网络传输
- API 友好度:Cookie 的
document.cookie是一个拼接字符串,操作不便;Web Storage 提供了setItem/getItem等标准 API - 作用域:Cookie 和 localStorage 在同源所有标签页共享;sessionStorage 仅限当前标签页
- 服务端访问:Cookie 可被服务端读写(通过请求头和
Set-Cookie响应头);Web Storage 只能通过客户端 JS 操作
Q2:什么是 HttpOnly Cookie?为什么重要?
答: 设置了 HttpOnly 标志的 Cookie,JavaScript 无法通过 document.cookie 读取或修改,只能由浏览器在发送 HTTP 请求时自动携带。
它的重要性在于防御 XSS 攻击。即使攻击者成功在页面注入了恶意脚本,也无法通过 document.cookie 窃取 HttpOnly Cookie 中的敏感信息(如登录令牌)。因此,所有用于身份认证的 Cookie 都应设置 HttpOnly。
Q3:SameSite Cookie 有哪些取值?分别什么作用?
答:
Strict:完全禁止跨站发送。即使用户从外部链接点入网站,也不会携带 Cookie。安全性最高但可能影响用户体验(如从搜索引擎点击链接需要重新登录)Lax(默认值):允许顶级导航的安全 GET 请求携带 Cookie(如<a>标签跳转),但阻止跨站 POST 和 iframe 中的请求。兼顾安全与体验None:允许所有跨站请求携带 Cookie,但必须同时设置Secure属性(仅 HTTPS)。适用于需要跨站的场景,如第三方登录、嵌入式支付
Q4:localStorage 能设置过期时间吗?怎么实现?
答: 原生的 localStorage API 不支持设置过期时间,数据会永久保留直到手动清除。但可以通过封装来实现:在存储数据时附加一个时间戳和过期时长,读取时检查是否已过期,过期则清除并返回 null。具体实现见本文"封装带过期时间的 localStorage"一节。
Q5:IndexedDB 和 localStorage 的区别是什么?什么场景该用 IndexedDB?
答: 主要区别:
| 维度 | localStorage | IndexedDB |
|---|---|---|
| 容量 | ~5-10MB | 数百 MB 甚至更大 |
| 数据类型 | 仅字符串 | 对象、数组、Blob 等结构化数据 |
| API 模式 | 同步 | 异步 |
| 查询能力 | 只能按 key 读取 | 支持索引、范围查询、游标遍历 |
| 事务 | 不支持 | 支持事务,保证数据一致性 |
| Web Worker | 不可用 | 可用 |
适合使用 IndexedDB 的场景:
- 离线应用需要存储大量数据(如邮件客户端、文档编辑器)
- 需要存储二进制文件(如图片、音频)
- 需要复杂的查询和索引能力
- 数据量超过 Web Storage 的限制
Q6:什么是 storage 事件?怎么用它实现跨标签页通信?
答: 当同源下某个标签页修改了 localStorage,其他标签页会触发 storage 事件。可以利用这个机制实现跨标签页通信。
// 标签页 A:监听消息
window.addEventListener('storage', (e) => {
if (e.key === 'message') {
console.log('收到消息:', e.newValue);
}
});
// 标签页 B:发送消息
localStorage.setItem('message', JSON.stringify({
text: '你好',
timestamp: Date.now(),
}));
注意点:
- 只有其他标签页的修改才会触发事件,当前标签页自身的修改不会触发
sessionStorage的修改不会触发storage事件- 除了
storage事件,还可以使用BroadcastChannel API实现更优雅的跨标签页通信
Q7:Cache API 和 HTTP 缓存(强缓存/协商缓存)有什么区别?
答: 两者的本质区别在于控制权不同:
- HTTP 缓存(强缓存/协商缓存)由服务端通过响应头(
Cache-Control、ETag、Last-Modified等)控制,浏览器自动管理,开发者无法精细控制缓存策略 - Cache API 由开发者在 Service Worker 中手动控制,可以自定义缓存策略(如 Cache First、Network First、Stale While Revalidate 等),拥有完全的编程控制能力
Cache API 通常用于 PWA 场景,实现离线访问和更灵活的缓存策略,而 HTTP 缓存是浏览器的内置机制,适用于所有网站。
Q8:浏览器的存储空间有上限吗?超出了会怎样?
答: 是的,浏览器对存储空间有配额限制,具体因浏览器而异:
- Cookie:每个域名约 4KB,总数通常不超过 50 个
- Web Storage:每个源约 5-10MB
- IndexedDB / Cache API:共享一个存储配额池,通常不超过磁盘可用空间的一定比例(Chrome 约为磁盘的 80%,但单源默认最多约 60%)
超出配额时:
- Web Storage 会抛出
QuotaExceededError异常 - IndexedDB 事务会失败并触发
error事件 - 可以通过
navigator.storage.estimate()查询当前的使用量和配额 - 调用
navigator.storage.persist()可以请求持久化存储(避免浏览器在空间不足时自动清理)
Q9:在 React/Vue 项目中,如何优雅地管理本地存储?
答: 推荐做法:
- 封装统一的存储层,屏蔽底层 API 差异,并处理序列化、容错、过期等逻辑
- 在 React 中使用自定义 Hook:
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setStoredValue = (newValue) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
};
return [value, setStoredValue];
}
// 使用
const [theme, setTheme] = useLocalStorage('theme', 'light');
- 在 Vue 中使用 VueUse 的
useStorage:
import { useStorage } from '@vueuse/core';
const theme = useStorage('theme', 'light');
这样做的好处是将存储逻辑与组件解耦,方便测试和维护。
Q10:如何清除一个域名下所有的浏览器存储数据?
答: 可以分别清除各种存储:
// 清除所有 Cookie(仅能清除非 HttpOnly 的)
document.cookie.split(';').forEach((c) => {
const name = c.split('=')[0].trim();
document.cookie = `${name}=; max-age=0; path=/`;
});
// 清除 Web Storage
localStorage.clear();
sessionStorage.clear();
// 清除所有 IndexedDB 数据库
const dbs = await indexedDB.databases();
dbs.forEach((db) => indexedDB.deleteDatabase(db.name));
// 清除所有 Cache Storage
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map((name) => caches.delete(name)));
在开发调试中,最便捷的方式是打开 DevTools → Application 面板 → 左侧 Storage → 点击 "Clear site data" 按钮,可以一键清除当前源的所有存储数据。