Appearance
Cookie 与 Web Storage 详解
导航目录
核心概念
Cookie、localStorage 和 sessionStorage 是浏览器端三种主要的本地存储方案。它们各有特点,适用于不同的应用场景。理解它们的差异对于前端开发至关重要。
目录
Cookie 详解
什么是 Cookie
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,主要用于:
- 会话状态管理:登录状态、购物车等
- 个性化设置:用户偏好、主题等
- 浏览器行为跟踪:分析用户行为
Cookie 核心属性
| 属性 | 说明 | 示例 |
|---|---|---|
| Name=Value | Cookie 的名称和值 | session_id=abc123 |
| Domain | 指定 Cookie 所属的域名 | .example.com(子域共享) |
| Path | 指定 Cookie 有效的路径 | /api(仅 API 路径) |
| Expires | 绝对过期时间(GMT 格式) | Wed, 21 Oct 2025 07:28:00 GMT |
| Max-Age | 相对有效期(秒),优先级高于 Expires | 3600(1 小时) |
| Secure | 仅通过 HTTPS 传输 | 敏感信息必须设置 |
| HttpOnly | 禁止 JavaScript 访问 | 防御 XSS 攻击 |
| SameSite | 控制跨站请求携带 | Strict / Lax / None |
Cookie 操作示例
javascript
/**
* 设置 Cookie
* @param {string} name - Cookie 名称
* @param {string} value - Cookie 值
* @param {Object} options - 配置选项
*/
function setCookie(name, value, options = {}) {
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (options.maxAge) {
cookieString += `; Max-Age=${options.maxAge}`;
}
if (options.expires) {
cookieString += `; Expires=${options.expires.toUTCString()}`;
}
if (options.path) {
cookieString += `; Path=${options.path}`;
}
if (options.domain) {
cookieString += `; Domain=${options.domain}`;
}
if (options.secure) {
cookieString += `; Secure`;
}
if (options.httpOnly) {
cookieString += `; HttpOnly`;
}
if (options.sameSite) {
cookieString += `; SameSite=${options.sameSite}`;
}
document.cookie = cookieString;
}
/**
* 获取 Cookie
* @param {string} name - Cookie 名称
* @returns {string|null} - Cookie 值
*/
function getCookie(name) {
const cookies = document.cookie.split("; ");
for (const cookie of cookies) {
const [key, value] = cookie.split("=").map(decodeURIComponent);
if (key === name) {
return value;
}
}
return null;
}
/**
* 删除 Cookie
* @param {string} name - Cookie 名称
* @param {Object} options - 配置选项
*/
function deleteCookie(name, options = {}) {
setCookie(name, "", {
...options,
maxAge: 0,
expires: new Date(0),
});
}
// 使用示例
setCookie("user_id", "12345", {
maxAge: 86400, // 1天
path: "/",
secure: true,
httpOnly: false, // 需要 JS 访问
sameSite: "Lax",
});
console.log(getCookie("user_id")); // "12345"SameSite 属性详解
SameSite 用于控制 Cookie 在跨站请求中的发送行为:
| 值 | 说明 | 适用场景 |
|---|---|---|
| Strict | 仅同站请求发送 Cookie | 高安全性场景(银行、支付) |
| Lax | 顶级导航 GET 请求可发送 | 默认推荐,平衡安全与体验 |
| None | 所有请求都发送 Cookie | 需配合 Secure 属性使用 |
javascript
// 高安全性设置(如银行网站)
setCookie("session_token", "xxx", {
sameSite: "Strict",
secure: true,
httpOnly: true,
});
// 普通网站推荐设置
setCookie("user_pref", "xxx", {
sameSite: "Lax",
secure: true,
});Web Storage
localStorage
localStorage 提供持久化的键值对存储,数据不会过期,除非手动删除。
基本操作
javascript
// 存储数据(自动转换为字符串)
localStorage.setItem("username", "John");
localStorage.setItem("settings", JSON.stringify({ theme: "dark" }));
// 读取数据
const username = localStorage.getItem("username");
const settings = JSON.parse(localStorage.getItem("settings") || "{}");
// 删除数据
localStorage.removeItem("username");
// 清空所有数据
localStorage.clear();
// 获取存储的键名
const key = localStorage.key(0);
// 获取存储数量
const length = localStorage.length;封装工具类
javascript
/**
* localStorage 封装类
* 支持过期时间、JSON 序列化
*/
class LocalStorageUtil {
/**
* 设置数据
* @param {string} key - 键名
* @param {*} value - 值
* @param {number} expire - 过期时间(毫秒)
*/
static set(key, value, expire = null) {
const data = {
value,
timestamp: Date.now(),
expire: expire ? Date.now() + expire : null,
};
localStorage.setItem(key, JSON.stringify(data));
}
/**
* 获取数据
* @param {string} key - 键名
* @returns {*} - 值或 null
*/
static get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
try {
const data = JSON.parse(raw);
// 检查是否过期
if (data.expire && Date.now() > data.expire) {
localStorage.removeItem(key);
return null;
}
return data.value;
} catch {
return raw;
}
}
/**
* 删除数据
* @param {string} key - 键名
*/
static remove(key) {
localStorage.removeItem(key);
}
/**
* 清空所有数据
*/
static clear() {
localStorage.clear();
}
}
// 使用示例
LocalStorageUtil.set("user", { name: "Alice", id: 1 }, 3600000); // 1小时过期
console.log(LocalStorageUtil.get("user"));跨窗口通信
javascript
// 监听 storage 事件(同域名下其他窗口修改时触发)
window.addEventListener("storage", (event) => {
console.log("键名:", event.key);
console.log("旧值:", event.oldValue);
console.log("新值:", event.newValue);
console.log("存储对象:", event.storageArea); // localStorage 或 sessionStorage
console.log("触发页面:", event.url);
});
// 示例:同步登录状态
window.addEventListener("storage", (e) => {
if (e.key === "login_status") {
const status = JSON.parse(e.newValue);
if (status.isLoggedIn) {
console.log("用户已登录,更新页面状态");
} else {
console.log("用户已登出,跳转登录页");
window.location.href = "/login";
}
}
});sessionStorage
sessionStorage 与 localStorage 类似,但数据仅在当前会话期间有效,关闭标签页后数据清除。
javascript
// 基本操作与 localStorage 相同
sessionStorage.setItem("temp_data", "xxx");
sessionStorage.getItem("temp_data");
sessionStorage.removeItem("temp_data");
sessionStorage.clear();
// 特点:
// 1. 页面刷新数据保留
// 2. 关闭标签页数据清除
// 3. 不同标签页数据不共享(即使是同一页面)复制 sessionStorage 到新标签页
html
<!-- 使用 rel="opener" 允许新标签页访问 opener 的 sessionStorage -->
<a href="./other.html" target="_blank" rel="opener">
打开新页面(共享 sessionStorage)
</a>javascript
// 在新页面中复制 sessionStorage
if (window.opener) {
const sessionData = window.opener.sessionStorage;
for (let i = 0; i < sessionData.length; i++) {
const key = sessionData.key(i);
sessionStorage.setItem(key, sessionData.getItem(key));
}
}三种存储方式对比
| 特性 | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| 存储容量 | 4KB | 5-10MB | 5-10MB |
| 生命周期 | 可配置(默认会话结束) | 永久(需手动清除) | 会话期间(关闭标签页清除) |
| 作用域 | 域名 + 路径 | 同源窗口共享 | 同源同窗口 |
| 服务器通信 | 自动携带在请求头中 | 不自动发送 | 不自动发送 |
| API 易用性 | 需手动解析字符串 | 简单易用 | 简单易用 |
| 安全性 | 可设置 HttpOnly、Secure | 无特殊保护 | 无特殊保护 |
| 适用场景 | 身份认证、跟踪 | 本地缓存、用户设置 | 临时表单数据 |
选择建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 用户登录状态 | Cookie + HttpOnly | 安全,自动随请求发送 |
| 购物车数据 | localStorage | 持久化,容量大 |
| 表单草稿 | sessionStorage | 临时存储,关闭即清 |
| 用户偏好设置 | localStorage | 长期保存,无需发送给服务器 |
| 跟踪分析 | Cookie | 可设置过期,自动携带 |
安全防护
Cookie 安全最佳实践
javascript
/**
* 设置安全的会话 Cookie
*/
function setSecureSessionCookie(sessionId) {
setCookie("session_id", sessionId, {
httpOnly: true, // 禁止 JavaScript 访问
secure: true, // 仅 HTTPS 传输
sameSite: "Strict", // 严格限制跨站
maxAge: 3600, // 1小时过期
path: "/", // 全站可用
});
}
/**
* 设置记住我 Cookie(长期有效)
*/
function setRememberMeCookie(token) {
setCookie("remember_token", token, {
httpOnly: true,
secure: true,
sameSite: "Lax",
maxAge: 30 * 24 * 3600, // 30天
});
}Web Storage 安全防护
XSS 风险
Web Storage 没有 HttpOnly 保护,容易受到 XSS 攻击。不要存储敏感信息(如密码、token)。
javascript
/**
* 安全的 Web Storage 封装
* 包含输入过滤和加密
*/
class SecureStorage {
static SECRET_KEY = "your-secret-key";
/**
* 转义 HTML 特殊字符
*/
static escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
/**
* 简单的加密(实际项目应使用专业库如 crypto-js)
*/
static encrypt(data) {
// 示例:Base64 编码 + 简单混淆
const str = JSON.stringify(data);
return btoa(unescape(encodeURIComponent(str)));
}
/**
* 解密
*/
static decrypt(encrypted) {
try {
const str = decodeURIComponent(escape(atob(encrypted)));
return JSON.parse(str);
} catch {
return null;
}
}
/**
* 安全存储
*/
static set(key, value, encrypt = false) {
const safeKey = this.escapeHtml(key);
const data = encrypt ? this.encrypt(value) : JSON.stringify(value);
localStorage.setItem(safeKey, data);
}
/**
* 安全读取
*/
static get(key, encrypted = false) {
const data = localStorage.getItem(key);
if (!data) return null;
try {
return encrypted ? this.decrypt(data) : JSON.parse(data);
} catch {
return data;
}
}
}
// 使用示例
SecureStorage.set("user_prefs", { theme: "dark" });
SecureStorage.set("sensitive_data", { apiKey: "xxx" }, true); // 加密存储性能优化
Cookie 优化
javascript
/**
* Cookie 优化策略
*/
const CookieOptimizer = {
/**
* 控制 Cookie 大小(不超过 4KB)
*/
checkSize(name, value) {
const size = new Blob([`${name}=${value}`]).size;
if (size > 4000) {
console.warn(`Cookie ${name} 过大: ${size} bytes`);
return false;
}
return true;
},
/**
* 精简 Cookie:只存储必要信息
*/
setMinimalCookie(name, value, options = {}) {
// 压缩值
const compressed = JSON.stringify(value).replace(/\s+/g, "");
if (this.checkSize(name, compressed)) {
setCookie(name, compressed, options);
}
},
/**
* 按路径隔离 Cookie
*/
setPathSpecificCookie(name, value, path) {
setCookie(name, value, { path });
},
};Web Storage 性能优化
javascript
/**
* Web Storage 性能优化工具
*/
class StorageOptimizer {
/**
* 计算存储空间使用情况
*/
static getStorageUsage() {
let totalSize = 0;
const encoder = new TextEncoder();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key) || "";
totalSize += encoder.encode(key).byteLength;
totalSize += encoder.encode(value).byteLength;
}
return {
bytes: totalSize,
kb: (totalSize / 1024).toFixed(2),
mb: (totalSize / 1024 / 1024).toFixed(2),
};
}
/**
* 智能清理策略
* @param {string} prefix - 键名前缀
* @param {number} keepCount - 保留数量
*/
static cleanup(prefix, keepCount = 10) {
const items = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(prefix)) {
try {
const data = JSON.parse(localStorage.getItem(key));
items.push({
key,
timestamp: data.timestamp || 0,
});
} catch {
items.push({ key, timestamp: 0 });
}
}
}
// 按时间排序,删除旧的
items.sort((a, b) => b.timestamp - a.timestamp);
items.slice(keepCount).forEach((item) => {
localStorage.removeItem(item.key);
console.log(`[清理] ${item.key}`);
});
}
/**
* 空间监控
*/
static monitor(maxSize = 4.5 * 1024 * 1024) {
// 4.5MB 预警
const usage = this.getStorageUsage();
if (usage.bytes > maxSize) {
console.warn(`⚠️ 存储空间超过 ${usage.mb}MB,触发自动清理`);
this.cleanup("cache:", 5);
}
return usage;
}
}
// 使用示例
console.log(StorageOptimizer.getStorageUsage());
StorageOptimizer.monitor();
StorageOptimizer.cleanup("app_cache:", 20);常见问题
Cookie 跨域共享
javascript
// 主域设置(注意前面的点号表示子域共享)
setCookie("shared_data", "xxx", {
domain: ".example.com", // 所有 example.com 子域可访问
path: "/",
});
// 子域读取(如 a.example.com)
console.log(getCookie("shared_data")); // "xxx"存储超出容量
javascript
/**
* 安全设置 localStorage,处理容量超限
*/
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === "QuotaExceededError") {
console.error("存储空间不足");
// 清理旧数据后重试
StorageOptimizer.cleanup("cache:", 5);
try {
localStorage.setItem(key, value);
return true;
} catch {
return false;
}
}
return false;
}
}隐私模式检测
javascript
/**
* 检测是否处于隐私模式
*/
function isPrivateMode() {
return new Promise((resolve) => {
try {
const testKey = "__private_test__";
localStorage.setItem(testKey, "1");
localStorage.removeItem(testKey);
resolve(false);
} catch {
resolve(true);
}
});
}
// 使用
isPrivateMode().then((private) => {
if (private) {
console.log("用户处于隐私模式,localStorage 不可用");
}
});最佳实践总结
- 敏感数据使用 Cookie + HttpOnly + Secure + SameSite
- 本地缓存使用 localStorage,注意容量限制
- 临时数据使用 sessionStorage,关闭即清
- 定期清理过期数据,避免存储溢出
- XSS 防护对存储数据进行过滤和转义
- 加密存储敏感信息(如使用 crypto-js)