Skip to content

Cookie 与 Web Storage 详解

导航目录

核心概念

Cookie、localStorage 和 sessionStorage 是浏览器端三种主要的本地存储方案。它们各有特点,适用于不同的应用场景。理解它们的差异对于前端开发至关重要。

目录


Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,主要用于:

  • 会话状态管理:登录状态、购物车等
  • 个性化设置:用户偏好、主题等
  • 浏览器行为跟踪:分析用户行为
属性说明示例
Name=ValueCookie 的名称和值session_id=abc123
Domain指定 Cookie 所属的域名.example.com(子域共享)
Path指定 Cookie 有效的路径/api(仅 API 路径)
Expires绝对过期时间(GMT 格式)Wed, 21 Oct 2025 07:28:00 GMT
Max-Age相对有效期(秒),优先级高于 Expires3600(1 小时)
Secure仅通过 HTTPS 传输敏感信息必须设置
HttpOnly禁止 JavaScript 访问防御 XSS 攻击
SameSite控制跨站请求携带Strict / Lax / None
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));
  }
}

三种存储方式对比

特性CookielocalStoragesessionStorage
存储容量4KB5-10MB5-10MB
生命周期可配置(默认会话结束)永久(需手动清除)会话期间(关闭标签页清除)
作用域域名 + 路径同源窗口共享同源同窗口
服务器通信自动携带在请求头中不自动发送不自动发送
API 易用性需手动解析字符串简单易用简单易用
安全性可设置 HttpOnly、Secure无特殊保护无特殊保护
适用场景身份认证、跟踪本地缓存、用户设置临时表单数据

选择建议

场景推荐方案原因
用户登录状态Cookie + HttpOnly安全,自动随请求发送
购物车数据localStorage持久化,容量大
表单草稿sessionStorage临时存储,关闭即清
用户偏好设置localStorage长期保存,无需发送给服务器
跟踪分析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); // 加密存储

性能优化

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);

常见问题

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 不可用");
  }
});

最佳实践总结

  1. 敏感数据使用 Cookie + HttpOnly + Secure + SameSite
  2. 本地缓存使用 localStorage,注意容量限制
  3. 临时数据使用 sessionStorage,关闭即清
  4. 定期清理过期数据,避免存储溢出
  5. XSS 防护对存储数据进行过滤和转义
  6. 加密存储敏感信息(如使用 crypto-js)