第5章 5.4 浏览器存储:localStorage/sessionStorage/IndexedDB

上章回顾:上一章我们学会了正则表达式这把"文本手术刀",能精准地匹配、查找、替换字符串了。但学完你会发现一个问题:正则匹配完的结果,一刷新浏览器就没了——数据没地方存啊!

本章目标:这一章我们要给网页装上一个"记忆口袋",学会在浏览器里保存数据。下课前你要做出一个能记住你输入内容的小工具。


🎯 开场 3 分钟:为什么需要浏览器存储?

你有没有遇到过这些情况?

  • 表单填了一半,刷新页面后所有内容都没了,气得想砸键盘
  • 做了一个网页版 Todo 清单,第二天打开发现昨天的待办全没了
  • 登录状态,关闭标签页再打开又要重新登录,烦死了

这些都是因为网页的数据存在内存里,关了就没了。就像在纸上写日记——纸烧了,日记就没了。

浏览器存储就是给你的网页装一个不会随页面关闭而消失的抽屉。这一章学完,你就能做出关掉再打开还在的 Todo 清单了。


🧱 基础\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\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 开发者工具

  1. 打开 Chrome → 按 F12 → 切换到 Application 标签
  2. 左侧找到 StorageLocal Storage / Session Storage
  3. 可以看到所有存储的 key-value
  4. 右键可以删除、编辑值
// 在代码里调试:打印所有存储
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 持久化,下一章我们把这些知识串起来,做一个能真正用的小工具!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。