第5章 5.4 浏览器存储:localStorage/sessionStorage/IndexedDB
上章回顾:上一章我们学会了正则表达式这把"文本手术刀",能精准地匹配、查找、替换字符串了。但学完你会发现一个问题:正则匹配完的结果,一刷新浏览器就没了——数据没地方存啊!
本章目标:这一章我们要给网页装上一个"记忆口袋",学会在浏览器里保存数据。下课前你要做出一个能记住你输入内容的小工具。
🎯 开场 3 分钟:为什么需要浏览器存储?
你有没有遇到过这些情况?
- 表单填了一半,刷新页面后所有内容都没了,气得想砸键盘
- 做了一个网页版 Todo 清单,第二天打开发现昨天的待办全没了
- 登录状态,关闭标签页再打开又要重新登录,烦死了
这些都是因为网页的数据存在内存里,关了就没了。就像在纸上写日记——纸烧了,日记就没了。
浏览器存储就是给你的网页装一个不会随页面关闭而消失的抽屉。这一章学完,你就能做出关掉再打开还在的 Todo 清单了。
🧱 基础\n\n
\n\n
\n\n 25 分钟:三种存储方式
5.4.1 localStorage - 永久保存的仓库
是什么:localStorage 是浏览器自带的一个永久仓库,容量约 5MB,数据除非你手动清除,否则永远存在。
生活类比:就像你家门口的报箱,快递员把包裹塞进去,你什么时候想取都行,而且不会过期。
为什么用:存用户偏好设置、长期登录状态、离线数据等。
怎么用:
// 存数据:key-value 形式,value 必须是字符串
localStorage.setItem('username', '小明');
localStorage.setItem('theme', 'dark');
// 取数据
const username = localStorage.getItem('username');
console.log(username); // 输出: 小明
// 删除数据
localStorage.removeItem('theme');
// 清空所有数据(慎用!)
// localStorage.clear();
代码解释:
- setItem 就像把东西放进抽屉
- getItem 就像从抽屉里拿东西
- removeItem 就像把抽屉里某个东西扔掉
5.4.2 sessionStorage - 临时工抽屉
是什么:sessionStorage 也是 key-value 存储,但只在当前标签页打开期间有效,关闭标签页就自动清空。
生活类比:就像餐厅的临时餐巾纸,用完就扔,换桌就没了。
为什么用:临时保存表单进度、验证码、页面间传递的临时数据。
怎么用:
// 用法和 localStorage 完全一样
sessionStorage.setItem('tempToken', 'abc123');
const tempToken = sessionStorage.getItem('tempToken');
console.log(tempToken); // 输出: abc123
// 关闭这个标签页后,tempToken 就自动消失了
关键区别:
| 特性 | localStorage | sessionStorage |
|---|---|---|
| 有效期 | 永久(手动清除) | 当前标签页 |
| 共享 | 同域名下共享 | 仅当前标签页 |
| 容量 | 约 5MB | 约 5MB |
// 在一个标签页存入,另一个标签页读取(localStorage 可以,sessionStorage 不行)
localStorage.setItem('test', 'hello');
// 打开新标签页
console.log(localStorage.getItem('test')); // 能读到 hello
5.4.3 IndexedDB - 大仓库
是什么:IndexedDB 是一个浏览器内置的数据库,能存大量结构化数据(音频、视频、文件等),容量大(几百 MB 到几 GB)。
生活类比:localStorage/sessionStorage 像你家门口的鞋柜,IndexedDB 像车库——能放车、放家具、放各种大件。
为什么用:做离线应用、缓存大量数据、存用户上传的文件。
为什么不用:用法复杂,新手劝退。如果你的数据不超过 5MB,先用 localStorage。
怎么用(最简单版本):
// 打开(或创建)一个数据库
const request = indexedDB.open('我的数据库', 1);
// 数据库创建/升级时触发
request.onupgradeneeded = function(event) {
const db = event.target.result;
// 创建存储空间(类似 Excel 的表)
const store = db.createObjectStore('用户信息', { keyPath: 'id' });
// 添加索引(方便查询)
store.createIndex('name', 'name', { unique: false });
};
// 成功打开数据库
request.onsuccess = function(event) {
const db = event.target.result;
console.log('数据库打开成功');
// 存数据(事务操作)
const transaction = db.transaction(['用户信息'], 'readwrite');
const store = transaction.objectStore('用户信息');
store.add({ id: 1, name: '小明', age: 18 });
// 取数据
const getRequest = store.get(1);
getRequest.onsuccess = function() {
console.log('取到的数据:', getRequest.result); // {id: 1, name: "小明", age: 18}
};
};
代码解释:
- indexedDB.open 打开数据库,第一次会自动创建
- onupgradeneeded 只在数据库版本变化时触发,用来建表
- transaction 事务保证数据一致性
- add 添加数据,get 获取数据
5.4.4 存复杂数据:JSON 大法
localStorage/sessionStorage 只能存字符串,数组、对象怎么办?
用 JSON.stringify() 转成字符串存进去,取出来时用 JSON.parse() 转回去:
// 存数组
const hobbies = ['篮球', '音乐', '编程'];
localStorage.setItem('hobbies', JSON.stringify(hobbies));
// 取出来
const savedHobbies = JSON.parse(localStorage.getItem('hobbies'));
console.log(savedHobbies); // ['篮球', '音乐', '编程']
// 存对象
const user = { name: '小红', score: 95 };
localStorage.setItem('user', JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.name); // 小红
坑来了:如果存进去的是对象,取的时候忘了 JSON.parse,直接当对象用会报错!
// ❌ 错误写法
const user = localStorage.getItem('user');
console.log(user.name); // undefined(因为 user 是字符串不是对象)
// ✅ 正确写法
const user = JSON.parse(localStorage.getItem('user'));
console.log(user.name); // 小红
🔥 实战 35 分钟:三个递进项目
项目 1(5 分钟):记住我上次输入的名字
需求:输入名字 → 点保存 → 刷新页面 → 名字还在。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>记住我 - localStorage 入门</title>
</head>
<body>
<h1>请告诉我你的名字</h1>
<input type="text" id="nameInput" placeholder="输入名字">
<button onclick="saveName()">保存</button>
<p id="welcome"></p>
<script>
// 页面加载时,读取保存的名字
window.onload = function() {
const savedName = localStorage.getItem('username');
if (savedName) {
document.getElementById('welcome').textContent = '欢迎回来,' + savedName + '!';
}
};
// 保存名字到 localStorage
function saveName() {
const name = document.getElementById('nameInput').value;
if (name) {
localStorage.setItem('username', name);
document.getElementById('welcome').textContent = '已保存!刷新看看~';
}
}
</script>
</body>
</html>
预期输出:输入"张三",点保存,刷新页面后显示"欢迎回来,张三!"
解释:页面加载时自动去 localStorage 读取 username,有就显示欢迎语。
项目 2(15 分钟):带分类的便签收藏夹
需求:收藏喜欢的网站链接,支持分类,刷新不丢失。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>便签收藏夹</title>
<style>
body { font-family: Arial; max-width: 600px; margin: 20px auto; padding: 0 20px; }
.card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 8px; }
.tag { background: #e0f0ff; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
input, select { padding: 8px; margin: 5px; }
button { padding: 8px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #45a049; }
</style>
</head>
<body>
<h1>📌 我的便签收藏夹</h1>
<div>
<input type="text" id="titleInput" placeholder="标题">
<input type="url" id="urlInput" placeholder="网址 https://...">
<select id="categorySelect">
<option value="技术">技术</option>
<option value="学习">学习</option>
<option value="娱乐">娱乐</option>
</select>
<button onclick="addBookmark()">添加收藏</button>
</div>
<div id="bookmarkList"></div>
<script>
// 从 localStorage 加载收藏
function loadBookmarks() {
const data = localStorage.getItem('bookmarks');
return data ? JSON.parse(data) : [];
}
// 保存收藏到 localStorage
function saveBookmarks(bookmarks) {
localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
}
// 渲染收藏列表
function renderBookmarks() {
const bookmarks = loadBookmarks();
const list = document.getElementById('bookmarkList');
if (bookmarks.length === 0) {
list.innerHTML = '<p>暂无收藏,点击上方添加~</p>';
return;
}
list.innerHTML = bookmarks.map((b, i) => `
<div class="card">
<strong>${b.title}</strong>
<span class="tag">${b.category}</span>
<a href="${b.url}" target="_blank">访问</a>
<button onclick="deleteBookmark(${i})" style="background:#ff4444;margin-left:10px;">删除</button>
</div>
`).join('');
}
// 添加收藏
function addBookmark() {
const title = document.getElementById('titleInput').value.trim();
const url = document.getElementById('urlInput').value.trim();
const category = document.getElementById('categorySelect').value;
if (!title || !url) {
alert('请填写标题和网址');
return;
}
const bookmarks = loadBookmarks();
bookmarks.push({ title, url, category });
saveBookmarks(bookmarks);
// 清空输入框
document.getElementById('titleInput').value = '';
document.getElementById('urlInput').value = '';
renderBookmarks();
}
// 删除收藏
function deleteBookmark(index) {
const bookmarks = loadBookmarks();
bookmarks.splice(index, 1);
saveBookmarks(bookmarks);
renderBookmarks();
}
// 页面加载时渲染
window.onload = renderBookmarks;
</script>
</body>
</html>
预期输出:添加几个收藏,关闭再打开浏览器,收藏还在;可以分类查看,可以删除。
解释:用 JSON 把数组转成字符串存进 localStorage,取出来时再转回数组。
项目 3(15 分钟):表单草稿自动保存
需求:写文章时,每隔 10 秒自动保存草稿到 localStorage,防止意外丢失。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>自动草稿箱</title>
<style>
body { font-family: Arial; max-width: 700px; margin: 20px auto; padding: 0 20px; }
textarea { width: 100%; height: 200px; padding: 10px; font-size: 16px; }
.status { color: #666; font-size: 14px; margin-top: 10px; }
.auto-saved { color: #4CAF50; }
</style>
</head>
<body>
<h1>📝 自动草稿箱</h1>
<p>写点什么吧!每 10 秒自动保存,刷新也不会丢失~</p>
<textarea id="editor" placeholder="开始写你的文章..."></textarea>
<div class="status" id="status"></div>
<button onclick="clearDraft()" style="margin-top:10px;padding:8px 15px;background:#ff4444;color:white;border:none;border-radius:4px;cursor:pointer;">清空草稿</button>
<script>
const editor = document.getElementById('editor');
const status = document.getElementById('status');
const DRAFT_KEY = 'article_draft';
const AUTO_SAVE_INTERVAL = 10000; // 10 秒
// 加载草稿
function loadDraft() {
const draft = localStorage.getItem(DRAFT_KEY);
if (draft) {
editor.value = draft;
status.textContent = '已恢复上次的草稿';
status.className = 'status auto-saved';
}
}
// 保存草稿
function saveDraft() {
const content = editor.value;
if (content.trim()) {
localStorage.setItem(DRAFT_KEY, content);
status.textContent = '✓ 草稿已自动保存 ' + new Date().toLocaleTimeString();
status.className = 'status auto-saved';
}
}
// 清空草稿
function clearDraft() {
if (confirm('确定要清空草稿吗?')) {
localStorage.removeItem(DRAFT_KEY);
editor.value = '';
status.textContent = '草稿已清空';
status.className = 'status';
}
}
// 监听输入,每秒检查是否需要保存
let lastSaved = '';
setInterval(() => {
if (editor.value !== lastSaved && editor.value.trim()) {
saveDraft();
lastSaved = editor.value;
}
}, 1000);
// 页面加载时恢复草稿
window.onload = loadDraft;
</script>
</body>
</html>
预期输出:在文本框输入内容,等待 10 秒,状态栏显示"草稿已自动保存";刷新页面,内容还在。
解释:用 setInterval 每秒检查内容是否有变化,有变化就自动保存到 localStorage。
💪 进阶 20 分钟:常见坑 + 调试技巧
坑 1:存对象忘了 JSON.parse
// ❌ 错误:取出来的不是对象
const user = localStorage.getItem('user');
console.log(user.name); // 报错:Cannot read property 'name' of undefined
// ✅ 正确:先 parse
const user = JSON.parse(localStorage.getItem('user'));
console.log(user.name); // 小红
坑 2:存数字当成字符串
// ❌ 错误:存进去变成字符串了
localStorage.setItem('score', 95);
const score = localStorage.getItem('score');
console.log(score + 5); // 输出:955(字符串拼接!)
// ✅ 正确:存之前转成数字,取出来也算术运算
localStorage.setItem('score', String(95));
const score = Number(localStorage.getItem('score'));
console.log(score + 5); // 输出:100
坑 3:跨域访问被阻止
// ❌ 错误:不同域名/子域无法共享
// a.example.com 设置的 localStorage,b.example.com 读不到
// ✅ 正确:同域名下才能共享
// www.example.com 和 api.example.com 也不算同域
坑 4:localStorage 写满会报错
// ❌ 错误:没处理存储满的情况
localStorage.setItem('bigData', hugeString); // QuotaExceededError!
// ✅ 正确:try-catch 捕获异常
try {
localStorage.setItem('bigData', hugeString);
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('存储空间满了,请清理一些数据');
}
}
坑 5:sessionStorage 跨标签页不共享
// 在标签页 A 设置
sessionStorage.setItem('token', 'abc');
// 在标签页 B 读取(读不到!)
const token = sessionStorage.getItem('token'); // null
// ✅ 正确场景:sessionStorage 只在同一标签页内有效
// 适合临时表单、多步向导等场景
调试技巧:Chrome 开发者工具
- 打开 Chrome → 按 F12 → 切换到 Application 标签
- 左侧找到 Storage → Local Storage / Session Storage
- 可以看到所有存储的 key-value
- 右键可以删除、编辑值
// 在代码里调试:打印所有存储
console.log('localStorage 所有内容:');
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(`${key}: ${localStorage.getItem(key)}`);
}
✏️ 练习题
练习 1(2 分钟):读取已存数据
- 输入:localStorage 中存了 {"name":"张三","age":20}
- 预期输出:控制台打印 张三
- 提示:用 JSON.parse 转换后取 name 属性
练习 2(2 分钟):判断是否有保存
- 输入:用 localStorage.getItem 取一个不存在的 key
- 预期输出:打印 null
- 提示:存之前先判断是否为 null
练习 3(3 分钟):修改项目 1
- 需求:在项目 1 基础上,增加"清空名字"按钮
- 预期:点清空后刷新页面,不再显示欢迎语
- 提示:调用 localStorage.removeItem
练习 4(5 分钟):计数器应用
- 需求:做一个页面访问次数统计,刷新后次数 +1
- 预期:第一次访问显示"第 1 次访问",第二次显示"第 2 次访问"...
- 提示:取出来 +1,再存回去
练习 5(5 分钟):修复报错
- 代码:
localStorage.setItem('data', {x: 1, y: 2});
console.log(localStorage.getItem('data').x);
- 预期输出:打印
1 - 提示:检查数据类型转换
📚 总结 + 资源
本文学了 3 件事:
1. localStorage 永久存储,适合用户偏好、登录状态
2. sessionStorage 临时存储,只在当前标签页有效
3. 复杂数据用 JSON.stringify/parse 转换后存储
延伸资源:
- MDN Web Docs - Web Storage API(官方文档,最权威)
- MDN - IndexedDB API(想用大仓库就看这个)
- 视频:B 站搜索「localStorage 实战」有大量教程
互动钩子:你在做网页时有没有遇到"数据丢了"的糟心事?是用什么方法解决的?评论区聊聊,老粉优先回复!
下章预告:学完了浏览器存储,是时候做个真正的 Todo App 了——带增删改查、带分类、带 localStorage 持久化,下一章我们把这些知识串起来,做一个能真正用的小工具!

评论(0)