第 9 节:本地存储 — 数据不丢的前端缓存

第 9 节:本地存储 --- 数据不丢的前端缓存

前面的章节我们陆续掌握了 DOM 操作、事件处理、表单校验,已经能够构建出功能完整的交互界面。但所有这些数据(学生列表、用户输入)都存在 JavaScript 的内存变量里------页面一刷新,一切归零 。要让数据在浏览器端持久化,我们需要学习浏览器提供的本地存储能力。这节的内容就像是给你的前端应用配了一个极简的"数据库",它的 API 只有几个方法,但足以解决绝大部分的轻量级持久化需求。

本节将解决三个核心问题

  1. 如何把数据存到浏览器里,刷新后依然能读出来?
  2. 为什么存对象之前必须 JSON.stringify,取出来之后必须 JSON.parse
  3. 如何封装一个 store.js,让存储操作安全、可维护,并处理容量上限和异常?

1. 两种浏览器存储:localStoragesessionStorage

浏览器提供了两个极其简单的键值对存储对象,它们的 API 完全相同,唯一的区别在于数据的生命周期

1.1 基本 CRUD

javascript 复制代码
// 存一条数据(键值对)
localStorage.setItem('username', '张三');

// 读取
localStorage.getItem('username');   // "张三"

// 删除一条
localStorage.removeItem('username');

// 清空当前域名下所有数据
localStorage.clear();

这两个对象都是按域名隔离 的,不同域名的数据互不相通,并且只能存储字符串

1.2 localStorage vs sessionStorage

特性 localStorage sessionStorage
生命周期 永久,直到被主动删除 仅在当前标签页会话期间存在
页面刷新 数据保留 数据保留
关闭标签页/浏览器 数据保留 数据立即清空
跨标签页共享 同域名下共享 不共享(每个标签页独立)
典型用途 记住登录状态、用户偏好、缓存列表 表单草稿、临时跳转参数

选择策略 :问自己"关掉这个标签页后,这条数据还应该存在吗?"应该 → localStorage;不该 → sessionStorage


2. 核心痛点:只能存字符串 ------ JSON.stringify / JSON.parse

所有存入 localStorage 的值都会被隐式转换为字符串,如果你直接存放对象或数组,得到的将是毫无用处的 "[object Object]"

javascript 复制代码
const student = { name: '张三', score: 92 };
localStorage.setItem('stu', student);
localStorage.getItem('stu');  // "[object Object]"  ❌ 完全无法使用

解决方法是手动序列化与反序列化 ,使用 JSON.stringifyJSON.parse

javascript 复制代码
// 存:对象 → JSON 字符串
localStorage.setItem('students', JSON.stringify(list));

// 取:JSON 字符串 → 对象
const raw = localStorage.getItem('students');
const list = raw === null ? [] : JSON.parse(raw);

后端对照 :这就是你每天都在做的序列化 / 反序列化------Jackson 的 writeValueAsStringreadValue,只不过换成了浏览器原生 API。


3. 封装 store.js:打造自己的前端数据仓库

直接裸写 localStorage 会带来三个问题:键名容易冲突、序列化逻辑散落各处、没有任何异常保护。最佳实践是将其封装成一个类似 DAO 的工具模块。

3.1 设计目标

  • 统一添加域名前缀,避免键名冲突。
  • 自动处理 JSON.stringify / parse,外部无需关心。
  • 读取不存在的数据时,返回默认值 而不是 null
  • 内部使用 try-catch 兜底,避免存储失败时整个页面崩溃。

3.2 核心实现

javascript 复制代码
(function(global) {
  'use strict';
  const PREFIX = 'app_';   // 自定义前缀

  // 拼装完整键名
  function fullKey(key) {
    return PREFIX + key;
  }

  // 保存:支持任意类型,失败返回 false
  function save(key, value) {
    try {
      localStorage.setItem(fullKey(key), JSON.stringify(value));
      return true;
    } catch (e) {
      console.error('[store] 保存失败', key, e);
      return false;
    }
  }

  // 读取:自动反序列化,不存在时返回默认值
  function load(key, defaultValue) {
    try {
      const raw = localStorage.getItem(fullKey(key));
      if (raw === null) return defaultValue;
      return JSON.parse(raw);
    } catch (e) {
      console.error('[store] 读取失败', key, e);
      return defaultValue;
    }
  }

  // 删除
  function remove(key) {
    try {
      localStorage.removeItem(fullKey(key));
      return true;
    } catch (e) {
      console.error('[store] 删除失败', key, e);
      return false;
    }
  }

  global.Store = { save, load, remove };
})(window);

使用示例:

javascript 复制代码
// 保存学生数组
Store.save('students', students);

// 读取(若不存在则返回空数组)
const list = Store.load('students', []);

// 删除
Store.remove('students');

4. 容量限制与异常场景

localStorage 的存储上限通常在 5MB 左右(因浏览器而异)。一旦超出配额,setItem 会抛出 QuotaExceededError;此外,在某些隐私模式下浏览器可能直接禁用存储,也会导致写入失败。

这些异常是你无法控制的,因此所有存储操作都应该被 try-catch 包裹 ,这正是 store.js 中每个方法都内置异常捕获的原因。当 save 返回 false 时,你可以友好地提醒用户清理缓存,而不是让整个页面业务逻辑中断。

核心原则:本地存储只适合存放轻量级数据(JSON 配置、小型列表),不要用它缓存大文件或图片的 base64 数据。


5. 综合实战:学生列表持久化

结合前几节的知识,我们可以构建一个完整的学生管理界面:页面刷新后,之前新增或删除的记录依然存在。

设计思路

  • 使用一个内存数组 students 作为当前数据源。
  • 页面加载时,通过 Store.load('students', DEFAULT_LIST) 初始化。
  • 每次对数组进行增、删、改后,立即调用 Store.save 将全量数据写回存储
  • 渲染函数负责根据数组生成 DOM(第6节),事件委托处理删除(第7节),表单校验保证输入合法(第8节)。

核心代码示例

javascript 复制代码
const DEFAULT_LIST = [
  { id: 'S001', name: '张三', score: 92 },
  { id: 'S002', name: '李四', score: 78 },
];

let students = Store.load('students', DEFAULT_LIST);

// 渲染表格(使用第6节的 createElement + textContent 模式)
function render() {
  // ...清空 tbody,遍历 students 生成 tr
}

// 新增学生(表单 submit 事件,已调用 e.preventDefault)
function onAdd(e) {
  e.preventDefault();
  // 从表单取值、校验(第8节)
  students.push({ id: newId, name: newName, score: 0 });
  Store.save('students', students);
  render();
}

// 删除学生(事件委托在 tbody,结合 closest 和 dataset)
tbody.addEventListener('click', (e) => {
  const delBtn = e.target.closest('[data-action="delete"]');
  if (!delBtn) return;
  const id = delBtn.closest('tr').dataset.id;
  students = students.filter(s => s.id !== id);   // 第4节 filter
  Store.save('students', students);
  render();
});

// 页面启动时首次渲染
render();

关键思想 :前端的数据持久化遵循"读-改-写 "模式------load(读)→ 修改内存数组 → save(写回)→ 重新渲染 UI。这和后端 JPA 的"查询实体 → 修改属性 → repository.save()"逻辑完全同构。


6. 小结

核心知识点 一句话解释
存储 API setItemgetItemremoveItemclear,只能存字符串
对象序列化 存用 JSON.stringify,取用 JSON.parse,不可省略
生命周期 需要跨会话保留 → localStorage;仅当前标签页 → sessionStorage
封装 store.js 统一前缀、自动序列化、默认值、异常保护,对外暴露 save/load/remove
容量与异常 上限约 5MB,必须 try-catch,切勿存放大文件

掌握了本地存储,你的前端应用就能拥有"记忆",刷新、关闭后数据依然留存。

相关推荐
IT_陈寒1 小时前
Vite的HMR在我项目上突然失效,排查三天找到离谱原因
前端·人工智能·后端
人间凡尔赛1 小时前
React Compiler 1.0 正式落地:告别 useMemo / useCallback,2026 前端性能优化的新范式
前端·性能优化·react
灵析表格1 小时前
灵析表格功能函数深度分析报告
前端·数据库·microsoft
Data_Journal1 小时前
掌握网页抓取中的分页:完整指南
java·服务器·前端
breeze jiang1 小时前
React useRef + Web Worker:避免大计算阻塞页面的通信方案
前端·javascript·react.js
fthux1 小时前
MCP协议开发实战:从零搭建AI Agent工具链
前端·人工智能·ai·开源·github
油丶酸萝卜别吃2 小时前
encrypt.js — 前端密码哈希工具说明文档
前端·javascript·哈希算法
黄贵根7 小时前
JavaScript实现教培行业意向登记系统
开发语言·javascript·ecmascript