Day-28-本地存储

Day 28: 本地存储

🎯 学习目标

  • 理解浏览器本地存储的概念
  • 掌握 localStorage 和 sessionStorage 的使用
  • 了解 IndexedDB 基础
  • 学会处理数据持久化

💡 核心概念

为什么需要本地存储?

  • 保存用户偏好设置
  • 缓存数据以提高性能
  • 离线应用支持
  • 减少服务器请求

存储方式对比

特性 Cookie localStorage sessionStorage IndexedDB
容量 4KB 5-10MB 5-10MB 大(几百MB)
过期 手动设置 永久 会话结束 永久
作用域 同源 同源 标签页 同源
API 原始 简单 简单 复杂

📝 localStorage API

基本操作

保存数据

// 保存字符串
localStorage.setItem('username', '张三');

// 保存对象(需要转换为 JSON)
const user = {
  name: '李四',
  age: 25,
  email: 'lisi@example.com'
};
localStorage.setItem('user', JSON.stringify(user));

// 使用属性语法
localStorage.theme = 'dark';

读取数据

// 读取字符串
const username = localStorage.getItem('username');

// 读取对象(需要解析 JSON)
const userJson = localStorage.getItem('user');
const user = JSON.parse(userJson);
console.log(user.name); // 李四

// 使用属性语法
const theme = localStorage.theme;

删除数据

// 删除单个项
localStorage.removeItem('username');

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

检查数据

// 获取键的数量
console.log(localStorage.length); // 2

// 根据索引获取键名
const key = localStorage.key(0);
console.log(key); // 'username'

// 检查某个键是否存在
if (localStorage.getItem('theme') !== null) {
  console.log('主题已保存');
}

📝 sessionStorage API

sessionStorage 与 localStorage API 完全相同,区别在于数据的生命周期。

// 保存数据(仅在当前会话有效)
sessionStorage.setItem('tempData', '临时数据');

// 读取数据
const data = sessionStorage.getItem('tempData');

// 删除数据
sessionStorage.removeItem('tempData');

🎮 实战示例

示例 1:用户偏好设置保存

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>用户偏好设置</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
      transition: background-color 0.3s;
    }
    body.light {
      background-color: #ffffff;
      color: #333333;
    }
    body.dark {
      background-color: #1a1a1a;
      color: #f0f0f0;
    }
    .settings {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    .setting-item {
      margin-bottom: 15px;
      padding: 10px;
      border-bottom: 1px solid #eee;
    }
    button {
      padding: 10px 20px;
      margin: 5px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      background-color: #007bff;
      color: white;
    }
    button:hover {
      background-color: #0056b3;
    }
    .message {
      padding: 10px;
      margin-top: 10px;
      border-radius: 4px;
      display: none;
    }
    .message.success {
      background-color: #d4edda;
      color: #155724;
    }
  </style>
</head>
<body class="light">
  <div class="settings">
    <h1>用户偏好设置</h1>
    
    <div class="setting-item">
      <label>主题选择:</label>
      <button onclick="setTheme('light')">浅色</button>
      <button onclick="setTheme('dark')">深色</button>
    </div>
    
    <div class="setting-item">
      <label for="fontSize">字体大小:</label>
      <input type="range" id="fontSize" min="12" max="24" value="16" 
             onchange="setFontSize(this.value)">
      <span id="fontSizeValue">16px</span>
    </div>
    
    <div class="setting-item">
      <label for="language">语言:</label>
      <select id="language" onchange="setLanguage(this.value)">
        <option value="zh">中文</option>
        <option value="en">English</option>
      </select>
    </div>
    
    <div id="message" class="message"></div>
  </div>

  <script>
    // 页面加载时恢复设置
    function loadSettings() {
      const theme = localStorage.getItem('theme') || 'light';
      const fontSize = localStorage.getItem('fontSize') || '16';
      const language = localStorage.getItem('language') || 'zh';
      
      applyTheme(theme);
      applyFontSize(fontSize);
      applyLanguage(language);
    }
    
    // 设置主题
    function setTheme(theme) {
      localStorage.setItem('theme', theme);
      applyTheme(theme);
      showMessage('主题已保存!');
    }
    
    function applyTheme(theme) {
      document.body.className = theme;
    }
    
    // 设置字体大小
    function setFontSize(size) {
      localStorage.setItem('fontSize', size);
      applyFontSize(size);
      showMessage('字体大小已保存!');
    }
    
    function applyFontSize(size) {
      document.body.style.fontSize = size + 'px';
      document.getElementById('fontSize').value = size;
      document.getElementById('fontSizeValue').textContent = size + 'px';
    }
    
    // 设置语言
    function setLanguage(lang) {
      localStorage.setItem('language', lang);
      applyLanguage(lang);
      showMessage('语言已保存!');
    }
    
    function applyLanguage(lang) {
      document.getElementById('language').value = lang;
    }
    
    // 显示消息
    function showMessage(text) {
      const messageEl = document.getElementById('message');
      messageEl.textContent = text;
      messageEl.className = 'message success';
      messageEl.style.display = 'block';
      
      setTimeout(() => {
        messageEl.style.display = 'none';
      }, 2000);
    }
    
    // 页面加载时恢复设置
    window.addEventListener('load', loadSettings);
  </script>
</body>
</html>

示例 2:表单数据自动保存

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>表单自动保存</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 600px;
      margin: 50px auto;
      padding: 20px;
    }
    .form-group {
      margin-bottom: 15px;
    }
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    input, textarea {
      width: 100%;
      padding: 8px;
      border: 1px solid #ddd;
      border-radius: 4px;
      box-sizing: border-box;
    }
    textarea {
      height: 100px;
      resize: vertical;
    }
    .buttons {
      margin-top: 20px;
    }
    button {
      padding: 10px 20px;
      margin-right: 10px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      background-color: #007bff;
      color: white;
    }
    button:hover {
      background-color: #0056b3;
    }
    .status {
      margin-top: 10px;
      padding: 10px;
      border-radius: 4px;
      display: none;
    }
    .status.saved {
      background-color: #d4edda;
      color: #155724;
    }
  </style>
</head>
<body>
  <h1>📝 联系表单(自动保存)</h1>
  <p>您的输入会自动保存到本地存储</p>
  
  <form id="contactForm">
    <div class="form-group">
      <label for="name">姓名:</label>
      <input type="text" id="name" name="name" placeholder="请输入姓名">
    </div>
    
    <div class="form-group">
      <label for="email">邮箱:</label>
      <input type="email" id="email" name="email" placeholder="请输入邮箱">
    </div>
    
    <div class="form-group">
      <label for="phone">电话:</label>
      <input type="tel" id="phone" name="phone" placeholder="请输入电话">
    </div>
    
    <div class="form-group">
      <label for="message">留言:</label>
      <textarea id="message" name="message" placeholder="请输入留言内容"></textarea>
    </div>
    
    <div class="buttons">
      <button type="submit">提交</button>
      <button type="button" onclick="clearForm()">清空</button>
    </div>
    
    <div id="status" class="status"></div>
  </form>

  <script>
    const STORAGE_KEY = 'contactFormData';
    const form = document.getElementById('contactForm');
    const statusEl = document.getElementById('status');
    
    // 页面加载时恢复数据
    window.addEventListener('load', () => {
      const savedData = localStorage.getItem(STORAGE_KEY);
      if (savedData) {
        const data = JSON.parse(savedData);
        document.getElementById('name').value = data.name || '';
        document.getElementById('email').value = data.email || '';
        document.getElementById('phone').value = data.phone || '';
        document.getElementById('message').value = data.message || '';
        showStatus('已恢复之前的输入', 'saved');
      }
    });
    
    // 监听所有输入框的变化
    const inputs = form.querySelectorAll('input, textarea');
    inputs.forEach(input => {
      input.addEventListener('input', saveFormData);
    });
    
    // 保存表单数据
    function saveFormData() {
      const formData = {
        name: document.getElementById('name').value,
        email: document.getElementById('email').value,
        phone: document.getElementById('phone').value,
        message: document.getElementById('message').value
      };
      
      localStorage.setItem(STORAGE_KEY, JSON.stringify(formData));
      showStatus('已自动保存', 'saved');
    }
    
    // 提交表单
    form.addEventListener('submit', (e) => {
      e.preventDefault();
      alert('表单已提交!');
      clearForm();
    });
    
    // 清空表单
    function clearForm() {
      form.reset();
      localStorage.removeItem(STORAGE_KEY);
      showStatus('已清空', 'saved');
    }
    
    // 显示状态
    function showStatus(message, className) {
      statusEl.textContent = message;
      statusEl.className = `status ${className}`;
      statusEl.style.display = 'block';
      
      setTimeout(() => {
        statusEl.style.display = 'none';
      }, 2000);
    }
  </script>
</body>
</html>

⚠️ 注意事项

1. 安全性

  • 不要存储敏感信息(密码、信用卡号等)
  • 本地存储可以被用户查看和修改
  • XSS 攻击可以读取本地存储

2. 数据类型

  • 只能存储字符串
  • 存储对象需要使用 JSON.stringify()
  • 读取对象需要使用 JSON.parse()

3. 存储限制

  • localStorage 通常有 5-10MB 限制
  • 不同浏览器限制不同
  • 存储满后会抛出异常

✍️ 练习任务

基础练习

  1. 创建一个简单的待办事项应用
  2. 使用 localStorage 保存待办事项
  3. 刷新页面后数据不丢失

进阶练习

  1. 实现完整的购物车功能
  2. 添加数量修改和删除功能
  3. 实时更新总价

实战挑战

  1. 创建一个笔记应用
  2. 支持创建、编辑、删除笔记
  3. 实现搜索和分类功能
  4. 数据持久化到 localStorage

💡 常见问题

Q: localStorage 和 sessionStorage 的区别?
A: localStorage 数据永久保存,sessionStorage 在标签页关闭后清除。

Q: 如何清除 localStorage 数据?
A: 使用 localStorage.clear() 或浏览器开发者工具。

Q: localStorage 有什么替代方案?
A: IndexedDB 适合存储大量结构化数据。

Q: 如何检测浏览器是否支持 localStorage?
A: 使用 try-catch 测试 localStorage 是否可用。

📚 拓展阅读

📝 总结

本地存储是现代 Web 应用的基础功能:

  • localStorage: 永久存储,适合用户偏好
  • sessionStorage: 临时存储,适合会话数据
  • 注意安全性和存储限制
  • 合理使用可以提高应用体验