🎯 课程目标

完成本课程后,你将能够:

  • 理解浏览器存储方案的区别和适用场景
  • 熟练使用localStorage进行数据持久化
  • 掌握sessionStorage的用法
  • 理解数据序列化和反序列化
  • 能够设计合理的数据存储策略

📖 浏览器存储概述

现代浏览器提供了多种客户端存储方案,每种方案都有其特点和适用场景。

✨ 浏览器存储方案对比

  • localStorage:持久化存储,关闭浏览器后仍保留,容量约5MB
  • sessionStorage:会话存储,关闭标签页后清除,容量约5MB
  • Cookie:随请求发送,容量小(约4KB),有过期时间
  • IndexedDB:大型结构化数据存储,容量大(数百MB)
  • Cache API:专门用于缓存请求响应

💾 localStorage基础

localStorage是浏览器提供的键值对存储方案,数据持久化且跨会话保留。

基本操作

// 检查是否支持localStorage if (typeof Storage !== 'undefined') { console.log('支持localStorage'); } else { console.log('不支持localStorage'); } // 存储数据(只能存储字符串) localStorage.setItem('username', '张三'); localStorage.setItem('age', '25'); localStorage.setItem('isVIP', 'true'); // 获取数据 const username = localStorage.getItem('username'); console.log(username); // '张三' // 删除数据 localStorage.removeItem('age'); // 清空所有数据 localStorage.clear();

存储对象和数组

const user = { name: '张三', age: 25, skills: ['JavaScript', 'Python'], isVIP: true }; // 使用JSON序列化存储对象 localStorage.setItem('user', JSON.stringify(user)); // 获取并解析 const savedUser = localStorage.getItem('user'); if (savedUser) { const user = JSON.parse(savedUser); console.log(user.name); // '张三' console.log(user.skills); // ['JavaScript', 'Python'] } // 数组的存储和读取 const colors = ['red', 'green', 'blue']; localStorage.setItem('colors', JSON.stringify(colors)); const savedColors = JSON.parse(localStorage.getItem('colors')); console.log(savedColors[0]); // 'red'

📦 实用存储函数封装

封装通用的存储函数,简化日常开发。

// 存储封装 const Storage = { // 存储 set(key, value) { try { const serialized = JSON.stringify(value); localStorage.setItem(key, serialized); return true; } catch (e) { console.error('存储失败:', e); return false; } }, // 获取 get(key, defaultValue = null) { try { const item = localStorage.getItem(key); if (item === null) return defaultValue; return JSON.parse(item); } catch (e) { console.error('读取失败:', e); return defaultValue; } }, // 删除 remove(key) { localStorage.removeItem(key); }, // 清空 clear() { localStorage.clear(); }, // 获取所有键 keys() { const keys = []; for (let i = 0; i < localStorage.length; i++) { keys.push(localStorage.key(i)); } return keys; }, // 检查是否存在 has(key) { return localStorage.getItem(key) !== null; }, // 设置过期时间(单位:毫秒) setWithExpiry(key, value, ttl) { const item = { value, expiry: Date.now() + ttl }; this.set(key, item); }, // 获取带过期时间的数据 getWithExpiry(key) { const itemStr = localStorage.getItem(key); if (!itemStr) return null; try { const item = JSON.parse(itemStr); if (Date.now() > item.expiry) { this.remove(key); return null; } return item.value; } catch (e) { return null; } } }; // 使用示例 Storage.set('user', { name: '张三', age: 25 }); const user = Storage.get('user'); console.log(user.name); // 设置7天过期的数据 Storage.setWithExpiry('token', 'abc123', 7 * 24 * 60 * 60 * 1000);

🔐 sessionStorage

sessionStorage与localStorage类似,但数据仅在当前会话(标签页)中保留。

// sessionStorage的基本用法 sessionStorage.setItem('sessionId', 'session_12345'); sessionStorage.setItem('tempData', JSON.stringify({ step: 2, timestamp: Date.now() })); const sessionId = sessionStorage.getItem('sessionId'); console.log(sessionId); // 'session_12345' // 关闭标签页后数据消失 // 刷新页面时数据保留 // 常用场景:表单数据临时保存 function saveFormData() { const formData = { name: document.getElementById('name').value, email: document.getElementById('email').value, content: document.getElementById('content').value }; sessionStorage.setItem('formDraft', JSON.stringify(formData)); } function loadFormData() { const draft = sessionStorage.getItem('formDraft'); if (draft) { const data = JSON.parse(draft); document.getElementById('name').value = data.name || ''; document.getElementById('email').value = data.email || ''; document.getElementById('content').value = data.content || ''; } } // 提交成功后清除草稿 function submitSuccess() { sessionStorage.removeItem('formDraft'); }

🗂️ 常见应用场景

localStorage在实际开发中的典型应用场景。

场景1:用户偏好设置

// 主题设置 function setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); } function initTheme() { const savedTheme = localStorage.getItem('theme') || 'light'; setTheme(savedTheme); } // 语言设置 function setLanguage(lang) { localStorage.setItem('language', lang); location.reload(); } // 字体大小 function setFontSize(size) { document.documentElement.style.fontSize = size + 'px'; localStorage.setItem('fontSize', size); } function initFontSize() { const savedSize = localStorage.getItem('fontSize'); if (savedSize) { setFontSize(parseInt(savedSize)); } } // 初始化 initTheme(); initFontSize();

场景2:购物车数据

const Cart = { getItems() { return Storage.get('cart', []); }, addItem(product) { const items = this.getItems(); const existing = items.find(item => item.id === product.id); if (existing) { existing.quantity += 1; } else { items.push({ ...product, quantity: 1 }); } Storage.set('cart', items); this.updateCount(); }, removeItem(productId) { const items = this.getItems().filter(item => item.id !== productId); Storage.set('cart', items); this.updateCount(); }, updateQuantity(productId, quantity) { const items = this.getItems(); const item = items.find(i => i.id === productId); if (item) { item.quantity = Math.max(1, quantity); Storage.set('cart', items); } }, clear() { Storage.remove('cart'); this.updateCount(); }, getTotal() { return this.getItems().reduce((sum, item) => { return sum + item.price * item.quantity; }, 0); }, updateCount() { const count = this.getItems().reduce((sum, item) => sum + item.quantity, 0); document.getElementById('cartCount').textContent = count; } };

场景3:用户登录状态

const Auth = { TOKEN_KEY: 'authToken', USER_KEY: 'currentUser', login(token, user) { Storage.set(this.TOKEN_KEY, token); Storage.set(this.USER_KEY, user); }, logout() { Storage.remove(this.TOKEN_KEY); Storage.remove(this.USER_KEY); window.location.href = '/login'; }, getToken() { return Storage.get(this.TOKEN_KEY); }, getUser() { return Storage.get(this.USER_KEY); }, isLoggedIn() { return !!this.getToken() && !!this.getUser(); }, // 自动登录检查 checkAuth() { if (this.isLoggedIn()) { // Token过期检查 const token = this.getToken(); if (this.isTokenExpired(token)) { this.logout(); return false; } return true; } return false; }, isTokenExpired(token) { // 这里应该是JWT解析或其他过期检查逻辑 // 示例简化版 const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000 < Date.now(); } }; // 页面加载时检查登录状态 document.addEventListener('DOMContentLoaded', () => { if (!Auth.isLoggedIn()) { window.location.href = '/login'; } });

⚠️ 注意事项和限制

注意事项说明
只能存储字符串对象和数组需要JSON序列化
容量限制约5MB,不同浏览器可能不同
仅限同源不同域名无法共享数据
明文存储敏感信息不应存储在localStorage
同步操作大量数据会影响页面性能
XSS风险恶意脚本可读取所有localStorage

🔒 安全建议

🚀 IndexedDB简介

当localStorage容量不足时,可以使用IndexedDB存储大量结构化数据。

// 打开数据库 const request = indexedDB.open('MyDatabase', 1); request.onerror = (event) => { console.error('数据库打开失败:', event.target.error); }; request.onsuccess = (event) => { const db = event.target.result; console.log('数据库打开成功'); // 开始事务 const transaction = db.transaction(['users'], 'readwrite'); const objectStore = transaction.objectStore('users'); // 添加数据 const user = { name: '张三', age: 25, email: 'zhangsan@example.com' }; objectStore.add(user); // 查询数据 const getRequest = objectStore.get(1); getRequest.onsuccess = () => { console.log('查询结果:', getRequest.result); }; }; request.onupgradeneeded = (event) => { const db = event.target.result; // 创建对象仓库(类似表) if (!db.objectStoreNames.contains('users')) { const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true }); objectStore.createIndex('name', 'name', { unique: false }); objectStore.createIndex('email', 'email', { unique: true }); } };

⚠️ 常见问题

问题原因分析解决方案
存储对象读取为nullJSON解析失败检查存储格式,使用try-catch
数据在隐私模式下丢失隐私模式限制存储检测并提示用户
存储空间满超过5MB限制清理旧数据或使用IndexedDB
数据不同步localStorage不跨设备使用服务器存储
修改对象后保存失败直接修改引用未重新存储修改后调用Storage.set

✅ 课后练习

练习要求:请独立完成以下练习任务:

  1. 练习 1:封装一个完整的Storage工具类,支持过期时间功能
  2. 练习 2:实现一个便签应用,数据使用localStorage存储
  3. 选做练习:使用IndexedDB实现一个笔记应用,支持分类和搜索