轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

  • TypeScript

  • Node.js

  • Vue.js

  • 工程化

  • 浏览器与Web API

    • HTML基础-语义化标签与文档结构
    • HTML基础-文本与排版标签
    • HTML基础-列表与表格
    • HTML表单-Input类型详解
    • HTML表单-表单元素与验证
    • HTML交互-多媒体元素
    • HTML工程化-模板与组件化
    • HTML工程化-性能优化
    • CSS基础-选择器与优先级
    • CSS基础-盒模型与布局基础
    • CSS基础-单位与颜色系统
    • CSS基础-文本与字体
    • CSS基础-背景、列表与表格样式
    • CSS布局-Flexbox完全指南
    • CSS布局-Grid网格布局
    • CSS布局-响应式设计实践
    • CSS进阶-动画与过渡
    • CSS进阶-渐变与阴影效果
    • CSS进阶-Transform与3D变换
    • CSS进阶-滤镜与混合模式
    • 现代CSS-CSS预处理器对比
    • 现代CSS-CSS-in-JS方案
    • 现代CSS-原子化CSS与Tailwind
    • CSS工程化-架构与规范
    • CSS工程化-性能优化
    • CSS工程化-PostCSS实战指南
    • Web API基础-DOM操作完全指南
    • Web API基础-事件处理与委托
    • Web API基础-BOM与浏览器环境
    • Web API存储-客户端存储方案
      • 一、Web Storage概述
        • 1.1 localStorage vs sessionStorage
        • 1.2 基本使用
      • 二、localStorage详解
        • 2.1 存储和读取数据
        • 2.2 封装Storage工具类
        • 2.3 监听Storage事件
      • 三、sessionStorage详解
        • 3.1 基本使用
        • 3.2 使用场景
      • 四、Cookie详解
        • 4.1 Cookie基础
        • 4.2 Cookie属性
        • 4.3 Cookie工具类
        • 4.4 SameSite属性详解
      • 五、存储方案对比
        • 5.1 三种方案对比
        • 5.2 选择建议
      • 六、存储限制与配额
        • 6.1 检测存储容量
        • 6.2 处理存储超限
      • 七、安全性考虑
        • 7.1 XSS攻击防护
        • 7.2 敏感数据处理
        • 7.3 Cookie安全
      • 八、综合示例
      • 九、最佳实践
        • 9.1 数据存储
        • 9.2 性能优化
        • 9.3 安全性
      • 十、总结
    • Web API网络-HTTP请求详解
    • Web API网络-实时通信方案
    • Web API交互-用户体验增强
    • HTML&CSS历代版本新特性
  • 前端
  • 浏览器与Web API
轩辕李
2019-09-19
目录

Web API存储-客户端存储方案

# Web API存储-客户端存储方案

客户端存储允许Web应用在浏览器中保存数据,实现离线访问、状态持久化等功能。本文将详细介绍localStorage、sessionStorage和Cookie三种主要的客户端存储方案。

# 一、Web Storage概述

Web Storage包括localStorage和sessionStorage,提供了简单的键值对存储API。

# 1.1 localStorage vs sessionStorage

特性 localStorage sessionStorage
生命周期 永久(除非手动清除) 标签页关闭后清除
作用域 同源下所有标签页共享 仅当前标签页
存储容量 通常5-10MB 通常5-10MB
API 相同 相同

# 1.2 基本使用

// localStorage和sessionStorage API完全相同

// 存储数据
localStorage.setItem('username', '张三');
localStorage.setItem('age', '25');

// 读取数据
const username = localStorage.getItem('username'); // "张三"
const age = localStorage.getItem('age');           // "25"

// 删除数据
localStorage.removeItem('age');

// 清空所有数据
localStorage.clear();

// 获取键名
const key = localStorage.key(0); // 获取第一个键名

// 获取存储项数量
const length = localStorage.length;

// 简写方式(不推荐,可能与方法名冲突)
localStorage.username = '李四';
const name = localStorage.username;
delete localStorage.username;

# 二、localStorage详解

# 2.1 存储和读取数据

// 存储字符串
localStorage.setItem('name', '张三');

// 存储数字(会转为字符串)
localStorage.setItem('age', 25);
console.log(typeof localStorage.getItem('age')); // "string"

// 存储对象(需要序列化)
const user = { name: '张三', age: 25 };
localStorage.setItem('user', JSON.stringify(user));

// 读取对象(需要反序列化)
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // "张三"

// 存储数组
const list = ['苹果', '香蕉', '橙子'];
localStorage.setItem('fruits', JSON.stringify(list));

// 读取数组
const storedList = JSON.parse(localStorage.getItem('fruits'));
console.log(storedList[0]); // "苹果"

// 存储日期(需要特殊处理)
const now = new Date();
localStorage.setItem('timestamp', now.toISOString());

// 读取日期
const storedDate = new Date(localStorage.getItem('timestamp'));
console.log(storedDate);

# 2.2 封装Storage工具类

class Storage {
  // 设置数据(自动序列化)
  static set(key, value, expire = null) {
    const data = {
      value: value,
      expire: expire ? Date.now() + expire : null
    };
    
    localStorage.setItem(key, JSON.stringify(data));
  }
  
  // 获取数据(自动反序列化,检查过期)
  static get(key) {
    const item = localStorage.getItem(key);
    
    if (!item) {
      return null;
    }
    
    try {
      const data = JSON.parse(item);
      
      // 检查是否过期
      if (data.expire && Date.now() > data.expire) {
        localStorage.removeItem(key);
        return null;
      }
      
      return data.value;
    } catch (err) {
      // 如果不是JSON格式,直接返回原值
      return item;
    }
  }
  
  // 删除数据
  static remove(key) {
    localStorage.removeItem(key);
  }
  
  // 清空所有数据
  static clear() {
    localStorage.clear();
  }
  
  // 获取所有键
  static keys() {
    return Object.keys(localStorage);
  }
  
  // 获取所有数据
  static getAll() {
    const result = {};
    
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      result[key] = this.get(key);
    }
    
    return result;
  }
  
  // 批量设置
  static setMultiple(obj) {
    Object.entries(obj).forEach(([key, value]) => {
      this.set(key, value);
    });
  }
  
  // 检查是否存在
  static has(key) {
    return localStorage.getItem(key) !== null;
  }
  
  // 获取存储大小(字节)
  static getSize() {
    let size = 0;
    
    for (let key in localStorage) {
      if (localStorage.hasOwnProperty(key)) {
        size += key.length + localStorage.getItem(key).length;
      }
    }
    
    return size;
  }
}

// 使用示例
Storage.set('user', { name: '张三', age: 25 });
Storage.set('token', 'abc123', 3600000); // 1小时后过期

const user = Storage.get('user');
console.log(user.name); // "张三"

Storage.setMultiple({
  theme: 'dark',
  language: 'zh-CN',
  sidebar: 'collapsed'
});

console.log(Storage.getAll());
console.log('存储大小:', Storage.getSize(), '字节');

# 2.3 监听Storage事件

// 监听storage变化(仅在其他标签页修改时触发)
window.addEventListener('storage', function(e) {
  console.log('Storage改变:');
  console.log('键名:', e.key);
  console.log('旧值:', e.oldValue);
  console.log('新值:', e.newValue);
  console.log('URL:', e.url);
  console.log('Storage对象:', e.storageArea);
});

// 实际应用:多标签页同步
window.addEventListener('storage', function(e) {
  if (e.key === 'theme') {
    // 当其他标签页修改主题时,同步更新
    applyTheme(e.newValue);
  }
  
  if (e.key === 'user' && e.newValue === null) {
    // 其他标签页登出,当前页面也登出
    logout();
  }
});

// 注意:storage事件不会在当前标签页触发
// 如果需要在当前页触发,需要手动派发自定义事件
function setItemWithEvent(key, value) {
  const oldValue = localStorage.getItem(key);
  localStorage.setItem(key, value);
  
  // 手动触发自定义事件
  window.dispatchEvent(new CustomEvent('localstorage-change', {
    detail: { key, oldValue, newValue: value }
  }));
}

# 三、sessionStorage详解

# 3.1 基本使用

// API与localStorage完全相同
sessionStorage.setItem('tempData', 'temporary');
const temp = sessionStorage.getItem('tempData');
sessionStorage.removeItem('tempData');
sessionStorage.clear();

# 3.2 使用场景

// 1. 表单数据临时保存
function saveFormData() {
  const formData = {
    name: document.getElementById('name').value,
    email: document.getElementById('email').value
  };
  
  sessionStorage.setItem('formDraft', JSON.stringify(formData));
}

// 恢复表单数据
function restoreFormData() {
  const draft = sessionStorage.getItem('formDraft');
  
  if (draft) {
    const data = JSON.parse(draft);
    document.getElementById('name').value = data.name;
    document.getElementById('email').value = data.email;
  }
}

// 表单提交成功后清除
function onFormSubmitSuccess() {
  sessionStorage.removeItem('formDraft');
}

// 2. 页面状态保存
// 保存滚动位置
window.addEventListener('scroll', () => {
  sessionStorage.setItem('scrollPosition', window.scrollY);
});

// 恢复滚动位置
window.addEventListener('load', () => {
  const scrollPos = sessionStorage.getItem('scrollPosition');
  if (scrollPos) {
    window.scrollTo(0, parseInt(scrollPos));
  }
});

// 3. 向导步骤数据
const wizard = {
  setStep(step, data) {
    sessionStorage.setItem(`wizard_step${step}`, JSON.stringify(data));
  },
  
  getStep(step) {
    const data = sessionStorage.getItem(`wizard_step${step}`);
    return data ? JSON.parse(data) : null;
  },
  
  clear() {
    const keys = Object.keys(sessionStorage);
    keys.forEach(key => {
      if (key.startsWith('wizard_')) {
        sessionStorage.removeItem(key);
      }
    });
  }
};

# 四、Cookie详解

# 4.1 Cookie基础

// 设置Cookie(最简单方式)
document.cookie = 'username=张三';

// 设置Cookie(带过期时间)
const expires = new Date();
expires.setDate(expires.getDate() + 7); // 7天后过期
document.cookie = `username=张三; expires=${expires.toUTCString()}`;

// 设置Cookie(带路径和域)
document.cookie = 'username=张三; path=/; domain=.example.com';

// 设置Cookie(完整选项)
document.cookie = 'token=abc123; max-age=3600; path=/; secure; samesite=strict';

// 读取Cookie
console.log(document.cookie); // "username=张三; token=abc123; ..."

# 4.2 Cookie属性

属性 说明 示例
expires 过期时间(GMT格式) expires=Wed, 21 Oct 2024 07:28:00 GMT
max-age 有效期(秒) max-age=3600
path 路径 path=/
domain 域名 domain=.example.com
secure 仅HTTPS传输 secure
httpOnly 仅服务端访问(JS无法访问) httpOnly
samesite 跨站请求限制 samesite=strict/lax/none

# 4.3 Cookie工具类

class Cookie {
  // 设置Cookie
  static set(name, value, options = {}) {
    let cookieStr = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
    
    // 过期时间
    if (options.expires) {
      if (typeof options.expires === 'number') {
        const date = new Date();
        date.setTime(date.getTime() + options.expires * 1000);
        options.expires = date;
      }
      cookieStr += `; expires=${options.expires.toUTCString()}`;
    }
    
    // max-age(优先级高于expires)
    if (options.maxAge) {
      cookieStr += `; max-age=${options.maxAge}`;
    }
    
    // 路径
    cookieStr += `; path=${options.path || '/'}`;
    
    // 域名
    if (options.domain) {
      cookieStr += `; domain=${options.domain}`;
    }
    
    // secure
    if (options.secure) {
      cookieStr += '; secure';
    }
    
    // samesite
    if (options.sameSite) {
      cookieStr += `; samesite=${options.sameSite}`;
    }
    
    document.cookie = cookieStr;
  }
  
  // 获取Cookie
  static get(name) {
    const cookies = document.cookie.split('; ');
    
    for (const cookie of cookies) {
      const [key, value] = cookie.split('=');
      
      if (decodeURIComponent(key) === name) {
        return decodeURIComponent(value);
      }
    }
    
    return null;
  }
  
  // 获取所有Cookie
  static getAll() {
    const cookies = {};
    const items = document.cookie.split('; ');
    
    items.forEach(item => {
      const [key, value] = item.split('=');
      cookies[decodeURIComponent(key)] = decodeURIComponent(value);
    });
    
    return cookies;
  }
  
  // 删除Cookie
  static remove(name, options = {}) {
    this.set(name, '', {
      ...options,
      maxAge: -1
    });
  }
  
  // 检查Cookie是否存在
  static has(name) {
    return this.get(name) !== null;
  }
}

// 使用示例
Cookie.set('username', '张三', { maxAge: 3600 }); // 1小时
Cookie.set('token', 'abc123', { 
  maxAge: 7 * 24 * 3600,  // 7天
  secure: true,
  sameSite: 'strict'
});

console.log(Cookie.get('username')); // "张三"
console.log(Cookie.getAll());

Cookie.remove('username');

# 4.4 SameSite属性详解

// Strict:完全禁止第三方Cookie
Cookie.set('session', 'xxx', { sameSite: 'strict' });
// 跨站点请求时不会发送此Cookie

// Lax(默认):允许部分第三方Cookie
Cookie.set('session', 'xxx', { sameSite: 'lax' });
// 导航到目标网址的GET请求会发送Cookie
// 跨站点的POST请求不会发送

// None:允许第三方Cookie(必须配合secure)
Cookie.set('tracking', 'xxx', { 
  sameSite: 'none',
  secure: true  // 必须为HTTPS
});

# 五、存储方案对比

# 5.1 三种方案对比

特性 localStorage sessionStorage Cookie
容量 5-10MB 5-10MB 4KB
生命周期 永久 标签页关闭 可设置过期时间
作用域 同源所有标签页 当前标签页 可设置path和domain
HTTP请求 不发送 不发送 自动发送
API 简单 简单 复杂
兼容性 IE8+ IE8+ 所有浏览器
用途 本地数据存储 临时数据存储 服务端会话、跨域

# 5.2 选择建议

// 使用localStorage:
// - 用户偏好设置
// - 本地缓存数据
// - 购物车(离线可用)
// - 草稿保存

// 使用sessionStorage:
// - 表单临时数据
// - 页面状态
// - 向导步骤数据
// - 一次性登录

// 使用Cookie:
// - 身份认证令牌
// - 跨子域共享数据
// - 需要服务端访问的数据
// - CSRF令牌

# 六、存储限制与配额

# 6.1 检测存储容量

// 检测localStorage可用空间
function getStorageSize() {
  let size = 0;
  
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      size += key.length + localStorage.getItem(key).length;
    }
  }
  
  return size;
}

// 测试最大容量
function testStorageLimit() {
  let testKey = 'storageTest';
  let data = '0123456789';
  let size = 0;
  
  try {
    while (true) {
      localStorage.setItem(testKey, data);
      data += data;
      size += data.length;
    }
  } catch (e) {
    console.log('最大容量约为:', size, '字节');
    localStorage.removeItem(testKey);
  }
}

// 检查是否超出配额
function checkQuota() {
  if (navigator.storage && navigator.storage.estimate) {
    navigator.storage.estimate().then(estimate => {
      console.log('已用空间:', estimate.usage);
      console.log('总配额:', estimate.quota);
      console.log('使用率:', (estimate.usage / estimate.quota * 100).toFixed(2) + '%');
    });
  }
}

# 6.2 处理存储超限

function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
    return true;
  } catch (e) {
    if (e.name === 'QuotaExceededError') {
      console.error('存储空间不足');
      
      // 策略1:清理过期数据
      clearExpiredData();
      
      // 策略2:删除最旧的数据
      removeOldestItem();
      
      // 策略3:通知用户
      alert('存储空间不足,已清理部分数据');
      
      // 重试
      try {
        localStorage.setItem(key, value);
        return true;
      } catch (e) {
        return false;
      }
    }
    
    return false;
  }
}

function clearExpiredData() {
  const now = Date.now();
  
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      try {
        const data = JSON.parse(localStorage.getItem(key));
        
        if (data.expire && now > data.expire) {
          localStorage.removeItem(key);
        }
      } catch (e) {
        // 忽略无法解析的数据
      }
    }
  }
}

function removeOldestItem() {
  let oldestKey = null;
  let oldestTime = Infinity;
  
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      try {
        const data = JSON.parse(localStorage.getItem(key));
        
        if (data.timestamp && data.timestamp < oldestTime) {
          oldestTime = data.timestamp;
          oldestKey = key;
        }
      } catch (e) {
        // 忽略无法解析的数据
      }
    }
  }
  
  if (oldestKey) {
    localStorage.removeItem(oldestKey);
  }
}

# 七、安全性考虑

# 7.1 XSS攻击防护

// ✗ 危险:直接存储用户输入
const userInput = getUserInput();
localStorage.setItem('data', userInput);

// ✓ 安全:清理用户输入
function sanitizeInput(input) {
  const div = document.createElement('div');
  div.textContent = input;
  return div.innerHTML;
}

const safeInput = sanitizeInput(getUserInput());
localStorage.setItem('data', safeInput);

// ✗ 危险:直接使用存储的HTML
const html = localStorage.getItem('data');
element.innerHTML = html;

// ✓ 安全:使用textContent
const text = localStorage.getItem('data');
element.textContent = text;

# 7.2 敏感数据处理

// ✗ 避免:明文存储敏感信息
localStorage.setItem('password', 'myPassword123');
localStorage.setItem('creditCard', '1234-5678-9012-3456');

// ✓ 推荐:服务端存储敏感信息,客户端只存储token
localStorage.setItem('token', 'encrypted-token');

// 如果必须在客户端存储,考虑加密
class SecureStorage {
  static encrypt(text, key) {
    // 使用加密库(如CryptoJS)
    // return CryptoJS.AES.encrypt(text, key).toString();
  }
  
  static decrypt(ciphertext, key) {
    // return CryptoJS.AES.decrypt(ciphertext, key).toString(CryptoJS.enc.Utf8);
  }
  
  static set(key, value, encryptionKey) {
    const encrypted = this.encrypt(JSON.stringify(value), encryptionKey);
    localStorage.setItem(key, encrypted);
  }
  
  static get(key, encryptionKey) {
    const encrypted = localStorage.getItem(key);
    if (!encrypted) return null;
    
    const decrypted = this.decrypt(encrypted, encryptionKey);
    return JSON.parse(decrypted);
  }
}

# 7.3 Cookie安全

// 敏感Cookie应设置HttpOnly(服务端设置)
// Set-Cookie: sessionId=xxx; HttpOnly; Secure; SameSite=Strict

// 客户端设置安全Cookie
Cookie.set('token', 'xxx', {
  secure: true,      // 仅HTTPS
  sameSite: 'strict', // 防止CSRF
  maxAge: 3600       // 限制有效期
});

// 避免在Cookie中存储敏感信息
// Cookie会在每次请求中发送,容易被拦截

# 八、综合示例

<html>
  <div id="storage-demo-c9d0e">
    <h3>客户端存储综合示例</h3>
    
    <div class="tabs-c9d0e">
      <button class="tab-btn-c9d0e active-c9d0e" data-tab="local">localStorage</button>
      <button class="tab-btn-c9d0e" data-tab="session">sessionStorage</button>
      <button class="tab-btn-c9d0e" data-tab="cookie">Cookie</button>
      <button class="tab-btn-c9d0e" data-tab="compare">对比</button>
    </div>
    
    <div class="tab-content-c9d0e">
      <div id="local-c9d0e" class="panel-c9d0e active-c9d0e">
        <h4>localStorage操作</h4>
        <div class="controls-c9d0e">
          <input type="text" id="local-key-c9d0e" placeholder="键名">
          <input type="text" id="local-value-c9d0e" placeholder="值">
          <button id="local-set-c9d0e">存储</button>
          <button id="local-get-c9d0e">读取</button>
          <button id="local-remove-c9d0e">删除</button>
          <button id="local-clear-c9d0e">清空</button>
        </div>
        <div class="result-c9d0e" id="local-result-c9d0e"></div>
        <div class="list-c9d0e" id="local-list-c9d0e"></div>
      </div>
      
      <div id="session-c9d0e" class="panel-c9d0e">
        <h4>sessionStorage操作</h4>
        <div class="controls-c9d0e">
          <input type="text" id="session-key-c9d0e" placeholder="键名">
          <input type="text" id="session-value-c9d0e" placeholder="值">
          <button id="session-set-c9d0e">存储</button>
          <button id="session-get-c9d0e">读取</button>
          <button id="session-remove-c9d0e">删除</button>
          <button id="session-clear-c9d0e">清空</button>
        </div>
        <div class="result-c9d0e" id="session-result-c9d0e"></div>
        <div class="list-c9d0e" id="session-list-c9d0e"></div>
      </div>
      
      <div id="cookie-c9d0e" class="panel-c9d0e">
        <h4>Cookie操作</h4>
        <div class="controls-c9d0e">
          <input type="text" id="cookie-key-c9d0e" placeholder="键名">
          <input type="text" id="cookie-value-c9d0e" placeholder="值">
          <input type="number" id="cookie-maxage-c9d0e" placeholder="有效期(秒)" value="3600">
          <button id="cookie-set-c9d0e">存储</button>
          <button id="cookie-get-c9d0e">读取</button>
          <button id="cookie-remove-c9d0e">删除</button>
        </div>
        <div class="result-c9d0e" id="cookie-result-c9d0e"></div>
        <div class="list-c9d0e" id="cookie-list-c9d0e"></div>
      </div>
      
      <div id="compare-c9d0e" class="panel-c9d0e">
        <h4>存储方案对比</h4>
        <table class="compare-table-c9d0e">
          <thead>
            <tr>
              <th>特性</th>
              <th>localStorage</th>
              <th>sessionStorage</th>
              <th>Cookie</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>容量</td>
              <td>5-10MB</td>
              <td>5-10MB</td>
              <td>4KB</td>
            </tr>
            <tr>
              <td>生命周期</td>
              <td>永久</td>
              <td>标签页关闭</td>
              <td>可设置</td>
            </tr>
            <tr>
              <td>作用域</td>
              <td>同源共享</td>
              <td>当前标签页</td>
              <td>可跨子域</td>
            </tr>
            <tr>
              <td>HTTP发送</td>
              <td>否</td>
              <td>否</td>
              <td>是</td>
            </tr>
            <tr>
              <td>API复杂度</td>
              <td>简单</td>
              <td>简单</td>
              <td>复杂</td>
            </tr>
          </tbody>
        </table>
        
        <div class="storage-info-c9d0e">
          <h5>当前存储状态</h5>
          <div class="info-item-c9d0e">
            <strong>localStorage项数:</strong> <span id="local-count-c9d0e">0</span>
          </div>
          <div class="info-item-c9d0e">
            <strong>sessionStorage项数:</strong> <span id="session-count-c9d0e">0</span>
          </div>
          <div class="info-item-c9d0e">
            <strong>Cookie项数:</strong> <span id="cookie-count-c9d0e">0</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</html>

<style>
  #storage-demo-c9d0e {
    font-family: sans-serif;
    max-width: 800px;
  }
  
  .tabs-c9d0e {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
    border-bottom: 2px solid #e0e0e0;
  }
  
  .tab-btn-c9d0e {
    padding: 10px 20px;
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: 14px;
    color: #666;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: all 0.2s;
  }
  
  .tab-btn-c9d0e:hover {
    color: #2196F3;
  }
  
  .tab-btn-c9d0e.active-c9d0e {
    color: #2196F3;
    border-bottom-color: #2196F3;
  }
  
  .panel-c9d0e {
    display: none;
    padding: 20px;
    background: #f9f9f9;
    border-radius: 8px;
  }
  
  .panel-c9d0e.active-c9d0e {
    display: block;
  }
  
  .panel-c9d0e h4 {
    margin: 0 0 16px 0;
  }
  
  .controls-c9d0e {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
    flex-wrap: wrap;
  }
  
  .controls-c9d0e input {
    flex: 1;
    min-width: 120px;
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
  }
  
  .controls-c9d0e button {
    padding: 8px 16px;
    background: #2196F3;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
    transition: background 0.2s;
  }
  
  .controls-c9d0e button:hover {
    background: #1976D2;
  }
  
  .result-c9d0e {
    padding: 12px;
    background: white;
    border-radius: 4px;
    margin-bottom: 12px;
    min-height: 40px;
    border: 1px solid #e0e0e0;
    font-family: monospace;
    font-size: 13px;
  }
  
  .list-c9d0e {
    background: white;
    border-radius: 4px;
    padding: 12px;
    border: 1px solid #e0e0e0;
    max-height: 200px;
    overflow-y: auto;
  }
  
  .list-item-c9d0e {
    padding: 8px;
    margin-bottom: 4px;
    background: #f5f5f5;
    border-radius: 3px;
    font-size: 13px;
    font-family: monospace;
    display: flex;
    justify-content: space-between;
  }
  
  .list-item-c9d0e strong {
    color: #2196F3;
  }
  
  .compare-table-c9d0e {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 20px;
    background: white;
  }
  
  .compare-table-c9d0e th,
  .compare-table-c9d0e td {
    padding: 12px;
    text-align: left;
    border: 1px solid #e0e0e0;
    font-size: 13px;
  }
  
  .compare-table-c9d0e th {
    background: #f5f5f5;
    font-weight: bold;
    color: #333;
  }
  
  .compare-table-c9d0e tbody tr:hover {
    background: #f9f9f9;
  }
  
  .storage-info-c9d0e {
    background: white;
    padding: 16px;
    border-radius: 4px;
    border: 1px solid #e0e0e0;
  }
  
  .storage-info-c9d0e h5 {
    margin: 0 0 12px 0;
  }
  
  .info-item-c9d0e {
    padding: 8px;
    margin-bottom: 4px;
    font-size: 14px;
  }
  
  .info-item-c9d0e strong {
    color: #666;
    min-width: 180px;
    display: inline-block;
  }
  
  .info-item-c9d0e span {
    color: #2196F3;
    font-weight: bold;
  }
</style>

<script>
  const tabs = document.querySelectorAll('.tab-btn-c9d0e');
  const panels = document.querySelectorAll('.panel-c9d0e');
  
  tabs.forEach(tab => {
    tab.addEventListener('click', () => {
      const targetTab = tab.dataset.tab;
      
      tabs.forEach(t => t.classList.remove('active-c9d0e'));
      panels.forEach(p => p.classList.remove('active-c9d0e'));
      
      tab.classList.add('active-c9d0e');
      document.getElementById(`${targetTab}-c9d0e`).classList.add('active-c9d0e');
      
      if (targetTab === 'compare') {
        updateStorageInfo();
      }
    });
  });

  function updateLocalList() {
    const list = document.getElementById('local-list-c9d0e');
    list.innerHTML = '<div style="color: #999; text-align: center;">localStorage内容</div>';
    
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      const value = localStorage.getItem(key);
      
      const item = document.createElement('div');
      item.className = 'list-item-c9d0e';
      item.innerHTML = `<span><strong>${key}:</strong> ${value.substring(0, 30)}${value.length > 30 ? '...' : ''}</span>`;
      list.appendChild(item);
    }
  }
  
  document.getElementById('local-set-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('local-key-c9d0e').value;
    const value = document.getElementById('local-value-c9d0e').value;
    
    if (key && value) {
      localStorage.setItem(key, value);
      document.getElementById('local-result-c9d0e').textContent = `✓ 已存储: ${key} = ${value}`;
      updateLocalList();
    }
  });
  
  document.getElementById('local-get-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('local-key-c9d0e').value;
    
    if (key) {
      const value = localStorage.getItem(key);
      document.getElementById('local-result-c9d0e').textContent = value !== null 
        ? `${key} = ${value}` 
        : `✗ 未找到键: ${key}`;
    }
  });
  
  document.getElementById('local-remove-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('local-key-c9d0e').value;
    
    if (key) {
      localStorage.removeItem(key);
      document.getElementById('local-result-c9d0e').textContent = `✓ 已删除: ${key}`;
      updateLocalList();
    }
  });
  
  document.getElementById('local-clear-c9d0e').addEventListener('click', () => {
    localStorage.clear();
    document.getElementById('local-result-c9d0e').textContent = '✓ 已清空localStorage';
    updateLocalList();
  });

  function updateSessionList() {
    const list = document.getElementById('session-list-c9d0e');
    list.innerHTML = '<div style="color: #999; text-align: center;">sessionStorage内容</div>';
    
    for (let i = 0; i < sessionStorage.length; i++) {
      const key = sessionStorage.key(i);
      const value = sessionStorage.getItem(key);
      
      const item = document.createElement('div');
      item.className = 'list-item-c9d0e';
      item.innerHTML = `<span><strong>${key}:</strong> ${value.substring(0, 30)}${value.length > 30 ? '...' : ''}</span>`;
      list.appendChild(item);
    }
  }
  
  document.getElementById('session-set-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('session-key-c9d0e').value;
    const value = document.getElementById('session-value-c9d0e').value;
    
    if (key && value) {
      sessionStorage.setItem(key, value);
      document.getElementById('session-result-c9d0e').textContent = `✓ 已存储: ${key} = ${value}`;
      updateSessionList();
    }
  });
  
  document.getElementById('session-get-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('session-key-c9d0e').value;
    
    if (key) {
      const value = sessionStorage.getItem(key);
      document.getElementById('session-result-c9d0e').textContent = value !== null 
        ? `${key} = ${value}` 
        : `✗ 未找到键: ${key}`;
    }
  });
  
  document.getElementById('session-remove-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('session-key-c9d0e').value;
    
    if (key) {
      sessionStorage.removeItem(key);
      document.getElementById('session-result-c9d0e').textContent = `✓ 已删除: ${key}`;
      updateSessionList();
    }
  });
  
  document.getElementById('session-clear-c9d0e').addEventListener('click', () => {
    sessionStorage.clear();
    document.getElementById('session-result-c9d0e').textContent = '✓ 已清空sessionStorage';
    updateSessionList();
  });

  function setCookie(name, value, maxAge) {
    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; max-age=${maxAge}; path=/`;
  }
  
  function getCookie(name) {
    const cookies = document.cookie.split('; ');
    for (const cookie of cookies) {
      const [key, value] = cookie.split('=');
      if (decodeURIComponent(key) === name) {
        return decodeURIComponent(value);
      }
    }
    return null;
  }
  
  function removeCookie(name) {
    document.cookie = `${encodeURIComponent(name)}=; max-age=-1; path=/`;
  }
  
  function updateCookieList() {
    const list = document.getElementById('cookie-list-c9d0e');
    list.innerHTML = '<div style="color: #999; text-align: center;">Cookie内容</div>';
    
    const cookies = document.cookie.split('; ');
    
    cookies.forEach(cookie => {
      if (cookie) {
        const [key, value] = cookie.split('=');
        const item = document.createElement('div');
        item.className = 'list-item-c9d0e';
        item.innerHTML = `<span><strong>${decodeURIComponent(key)}:</strong> ${decodeURIComponent(value).substring(0, 20)}...</span>`;
        list.appendChild(item);
      }
    });
  }
  
  document.getElementById('cookie-set-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('cookie-key-c9d0e').value;
    const value = document.getElementById('cookie-value-c9d0e').value;
    const maxAge = document.getElementById('cookie-maxage-c9d0e').value || 3600;
    
    if (key && value) {
      setCookie(key, value, maxAge);
      document.getElementById('cookie-result-c9d0e').textContent = `✓ 已存储: ${key} = ${value} (${maxAge}秒)`;
      updateCookieList();
    }
  });
  
  document.getElementById('cookie-get-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('cookie-key-c9d0e').value;
    
    if (key) {
      const value = getCookie(key);
      document.getElementById('cookie-result-c9d0e').textContent = value !== null 
        ? `${key} = ${value}` 
        : `✗ 未找到键: ${key}`;
    }
  });
  
  document.getElementById('cookie-remove-c9d0e').addEventListener('click', () => {
    const key = document.getElementById('cookie-key-c9d0e').value;
    
    if (key) {
      removeCookie(key);
      document.getElementById('cookie-result-c9d0e').textContent = `✓ 已删除: ${key}`;
      updateCookieList();
    }
  });

  function updateStorageInfo() {
    document.getElementById('local-count-c9d0e').textContent = localStorage.length;
    document.getElementById('session-count-c9d0e').textContent = sessionStorage.length;
    document.getElementById('cookie-count-c9d0e').textContent = document.cookie.split('; ').filter(c => c).length;
  }
  
  updateLocalList();
  updateSessionList();
  updateCookieList();
</script>

# 九、最佳实践

# 9.1 数据存储

  1. 使用JSON序列化存储复杂对象
  2. 添加版本号,方便数据迁移
  3. 为数据添加时间戳,便于清理
  4. 使用命名空间避免冲突

# 9.2 性能优化

  1. 避免频繁读写Storage
  2. 批量操作时使用缓存
  3. 大数据使用IndexedDB
  4. 定期清理过期数据

# 9.3 安全性

  1. 不存储敏感信息
  2. 清理用户输入
  3. Cookie使用secure和httpOnly
  4. 考虑数据加密

# 十、总结

客户端存储提供了多种数据持久化方案:

  1. localStorage:永久存储,适合本地数据缓存
  2. sessionStorage:临时存储,适合页面状态
  3. Cookie:小容量存储,适合服务端会话

合理选择存储方案,可以提升用户体验,实现离线功能和状态持久化。

祝你变得更强!

编辑 (opens new window)
#Web API#Storage#localStorage#sessionStorage#Cookie
上次更新: 2025/11/28
Web API基础-BOM与浏览器环境
Web API网络-HTTP请求详解

← Web API基础-BOM与浏览器环境 Web API网络-HTTP请求详解→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式