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 限制
- 不同浏览器限制不同
- 存储满后会抛出异常
✍️ 练习任务
基础练习
- 创建一个简单的待办事项应用
- 使用 localStorage 保存待办事项
- 刷新页面后数据不丢失
进阶练习
- 实现完整的购物车功能
- 添加数量修改和删除功能
- 实时更新总价
实战挑战
- 创建一个笔记应用
- 支持创建、编辑、删除笔记
- 实现搜索和分类功能
- 数据持久化到 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: 临时存储,适合会话数据
- 注意安全性和存储限制
- 合理使用可以提高应用体验