使用 LocalStorage 实现本地待办事项(To-Do)列表

在现代 Web 开发中,前端开发者常常需要在浏览器中持久化存储一些用户数据。比如用户的偏好设置、表单草稿、或者像本文要实现的------一个简单的待办事项(To-Do List)应用。HTML5 提供了一个非常实用的 API:localStorage,它允许我们在用户的浏览器中以键值对的形式永久保存数据。

本文将通过一个完整的示例,带你了解如何使用 localStorage 实现一个可持久化存储的 To-Do 列表,并结合 JavaScript 的函数式编程思想和事件委托机制,写出结构清晰、易于维护的代码。


什么是 localStorage?

localStorage 是 Web Storage API 的一部分,用于在浏览器中长期存储数据。它的特点包括:

  • 持久性:除非用户手动清除,否则数据不会过期。
  • 键值对存储:只能存储字符串类型的数据(key 和 value 都是字符串)。
  • 同源策略 :只有同协议、同域名、同端口的页面才能共享同一个 localStorage

由于 localStorage 只能存字符串,因此我们通常会配合 JSON.stringify()JSON.parse() 来存储和读取对象或数组。


项目结构概览

我们的目标是实现一个简单的"Tapas"(小食)清单,用户可以:

  • 添加新的 Tapas 项;
  • 勾选已完成的项;
  • 页面刷新后数据依然保留。

HTML 结构如下:

javascript 复制代码
<div class="wrapper">
  <h2>LOCAL TAPAS</h2>
  <ul class="plates">
    <li>Loading Tapas...</li>
  </ul>
  <form class="add-items">
    <input type="text" placeholder="Item Name" required name="item">
    <input type="submit" value="+ Add Item">
  </form>
</div>

核心逻辑集中在 <script> 标签中,下面我们逐步拆解。


初始化数据与渲染列表

首先,我们从 localStorage 中读取已有的待办事项:

ini 复制代码
const items = JSON.parse(localStorage.getItem('todos')) || [];

如果 localStorage 中没有 'todos' 这个 key,就返回一个空数组作为默认值。

接着,我们封装一个 populateList 函数,用于将数据渲染到页面上:

ini 复制代码
function populateList(plates = [], platesList) {
  platesList.innerHTML = plates.map((plate, i) => {
    return `
      <li>
        <input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''} />
        <label for="item${i}">${plate.text}</label>
      </li>
    `;
  }).join('');
}

这个函数接收两个参数:

  • plates:待办事项数组;
  • platesList:要渲染到的 DOM 元素(即 <ul class="plates">)。

通过 map 方法生成每个列表项的 HTML 字符串,并用 join('') 拼接成完整内容,最后赋值给 innerHTML。这种方式避免了频繁操作 DOM,性能更优。

调用一次即可完成初始渲染:

scss 复制代码
populateList(items, itemsList);

添加新事项

当用户在表单中输入内容并点击"+ Add Item"时,会触发 submit 事件。我们通过事件监听器处理:

arduino 复制代码
addItems.addEventListener('submit', addItem);

addItem 函数的核心逻辑如下:

javascript 复制代码
function addItem(e) {
  e.preventDefault(); // 阻止表单默认提交(页面跳转)
  const text = this.querySelector('[name=item]').value.trim();
  //通过属性选择器 拿到表单中name为item的input中的值 并去除左右空格 这里其实也运用到包装类的概念
  //因为拿到的value是字符串 简单数据类型 但是可以调用方法.trim()
  const item = {
    text,
    done: false
    //通过done来控制勾选框的选中 一开始设置为false
  };
  
  items.push(item);//把输入的item添加到items
  localStorage.setItem('todos', JSON.stringify(items)); // 持久化存储
  populateList(items, itemsList); // 重新渲染
  this.reset(); // 清空表单内容 优化用户体验
}

这里有几个关键点:

  • 使用 e.preventDefault() 防止页面刷新;
  • 通过 this(指向表单元素)获取输入框的值;
  • 构造新对象 { text, done: false } 并加入数组;
  • 调用 localStorage.setItem() 将整个数组转为字符串后保存;
  • 重新渲染列表并清空表单。

勾选/取消勾选事项(事件委托)

如果为每个复选框单独绑定事件,效率低下且难以维护。因此我们采用事件委托 :将点击事件绑定在父容器 <ul class="plates"> 上,通过判断点击目标是否为 input 来决定是否处理。

ini 复制代码
itemsList.addEventListener('click', toggleDone);

function toggleDone(e) {
  const el = e.target;
  //指定  当点击的为input时 才进行下面操作
  if (el === 'INPUT') {
    const index = el.dataset.index;
    items[index].done = !items[index].done;
    //通过done来控制勾选 点击取反
    localStorage.setItem('todos', JSON.stringify(items));
    //点击后修改了item中的done值 再次进行本地存储
    populateList(items, itemsList);//再次渲染
  }
}

每次切换状态后,我们同样更新 localStorage 并重新渲染整个列表,确保视图与数据一致。


为什么使用函数式封装?

文中提到:"拒绝流程式代码,超过10行就封装函数"。这其实是一种良好的编程习惯:

  • 提高可读性populateListaddItem 等函数名清晰表达了意图;
  • 便于复用:渲染逻辑被抽离,可在多处调用;
  • 降低耦合:数据操作与 DOM 操作分离,逻辑更清晰;
  • 易于测试与调试:每个函数职责单一。

此外,默认参数(如 plates = [])和解构赋值(如 { text, done })也体现了 ES6 的简洁语法优势。


总结

通过这个小小的"Local Tapas"项目,我们不仅实现了基本的增删改查(CRUD)功能,更重要的是掌握了以下核心知识点:

  1. localStorage 的基本用法 :存储字符串、配合 JSON 处理复杂数据;
  2. 事件委托:高效处理动态生成元素的交互;
  3. 函数式编程思想:封装细节、关注输入输出;
  4. DOM 操作优化:批量更新而非逐个操作。

虽然这个应用很简单,但它展示了现代前端开发中"数据驱动视图"的典型模式:数据变化 → 更新存储 → 重新渲染。这种模式也是 React、Vue 等框架的思想基础。

你可以在此基础上继续扩展,比如:

  • 添加删除按钮;
  • 支持编辑事项;
  • 按完成状态过滤;
  • 加入动画效果等。

但无论如何,记住:好的代码始于清晰的结构和合理的抽象 。而 localStorage,正是你构建离线优先(Offline-First)应用的第一步。


本文代码已通过实际测试,可直接运行。欢迎在评论区交流你的改进想法!

相关推荐
Jing_Rainbow1 小时前
【前端三剑客-6/Lesson11(2025-10-28)构建现代响应式网页:从 HTML 到 CSS 弹性布局再到 JavaScript 交互的完整指南 🌈
前端·javascript
6***37941 小时前
JavaScript虚拟现实开发
开发语言·javascript·vr
非专业程序员1 小时前
精读 GitHub - servo 浏览器(一)
前端·ios·rust
Yanni4Night1 小时前
掌握 JS 中迭代器的未来用法
前端·javascript
Irene19911 小时前
Element UI 及其 Vue 3 版本 Element Plus 发展现状
前端·vue.js·ui
Account_Ray1 小时前
vue3 的专属二维码组件 vue3-next-qrcode 迎来 4.0.0 版本
前端·vue.js·nuxt.js
BBB努力学习程序设计1 小时前
Web App开发入门:页面分析与环境准备全攻略
前端·html
BBB努力学习程序设计1 小时前
超好用的轮播图神器:Swiper插件入门指南
前端·html
帧栈2 小时前
开发避坑指南(70):Vue3 Http请求头携带token下载pdf文件解决方案
前端·vue.js