简单待办列表
你将构建什么
一个待办列表应用:输入框添加新待办,列表显示所有待办,每条可以标记完成(划线效果)或删除。用数组管理数据,DOM 根据数据动态渲染。
学习目标
- 用数组管理待办数据,DOM 根据数据渲染
- 实现添加、标记完成、删除三种操作
- 用事件委托处理列表项的交互
- 理解数据驱动 UI 的基本模式
前置要求
- 了解 JavaScript 数组方法(push、filter、map)
- 了解 DOM 操作和事件监听
- 已完成前四个 JS 项目或具备同等基础
步骤
- 1
搭建界面骨架
HTML 中放输入框、添加按钮、待办列表容器。列表用 ul,初始为空。
代码<input type="text" id="todo-input" placeholder="输入待办内容..." /> <button id="add-btn">添加</button> <ul id="todo-list"></ul>预期结果
页面显示输入框、按钮和空列表。
提示
ul 列表初始为空,后续用 JS 动态添加 li。
- 2
管理待办数据
用数组 todos 管理数据,每个元素是 { id, text, completed } 对象。编写 addTodo 函数:创建新待办对象,push 到数组,触发渲染。
代码let todos = []; let nextId = 1; function addTodo(text) { todos.push({ id: nextId++, text, completed: false }); render(); }预期结果
调用 addTodo(“学习 JS”) 后数组新增一条待办。
提示
用 nextId 保证 id 唯一递增,比用数组长度更可靠(删除后长度会变)。
- 3
实现渲染函数
编写 render 函数:清空列表容器,遍历数组为每条待办创建 li,包含文本、完成按钮和删除按钮。已完成的加划线样式。
代码const listEl = document.querySelector('#todo-list'); function render() { listEl.innerHTML = ''; todos.forEach(todo => { const li = document.createElement('li'); li.innerHTML = ` <span class="${todo.completed ? 'done' : ''}">${todo.text}</span> <button data-action="toggle" data-id="${todo.id}">完成</button> <button data-action="delete" data-id="${todo.id}">删除</button> `; listEl.appendChild(li); }); }预期结果
调用 render 后列表显示所有待办项。
提示
每次重新渲染整个列表虽然简单,但大数据量时可以用差量更新优化。
- 4
用事件委托处理列表交互
给列表容器绑定 click 事件(事件委托),根据 data-action 判断是完成还是删除,根据 data-id 找到对应待办并操作数组,再重新渲染。
代码listEl.addEventListener('click', (e) => { const action = e.target.dataset.action; const id = parseInt(e.target.dataset.id); if (!action) return; if (action === 'toggle') { todos = todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t ); } else if (action === 'delete') { todos = todos.filter(t => t.id !== id); } render(); });预期结果
点击“完成”切换划线状态,点击“删除”移除待办。
提示
事件委托把事件绑在父元素上,利用冒泡统一处理——比给每个按钮绑事件更高效。
- 5
绑定添加功能并测试
给添加按钮绑定 click 事件:读取输入框值,非空时调用 addTodo 并清空输入框。回车也能添加。测试完整流程。
代码const inputEl = document.querySelector('#todo-input'); const addBtn = document.querySelector('#add-btn'); function handleAdd() { const text = inputEl.value.trim(); if (text) { addTodo(text); inputEl.value = ''; } } addBtn.addEventListener('click', handleAdd); inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleAdd(); });预期结果
输入文字点击添加或按回车,新待办出现在列表中。
提示
trim() 去掉首尾空格,避免添加纯空格的待办。
自己动手试试
<input type="text" id="todo-input" placeholder="输入待办内容..." />
<button id="add-btn">添加</button>
<ul id="todo-list"></ul>
<script>
let todos = [];
let nextId = 1;
// TODO: render()、添加按钮事件、列表事件委托
</script><input type="text" id="todo-input" placeholder="输入待办内容..." />
<button id="add-btn">添加</button>
<ul id="todo-list"></ul>
<script>
let todos = [];
let nextId = 1;
const listEl = document.querySelector('#todo-list');
const inputEl = document.querySelector('#todo-input');
const addBtn = document.querySelector('#add-btn');
function render() {
listEl.innerHTML = '';
todos.forEach(todo => {
const li = document.createElement('li');
li.innerHTML = `
<span class="${todo.completed ? 'done' : ''}">${todo.text}</span>
<button data-action="toggle" data-id="${todo.id}">完成</button>
<button data-action="delete" data-id="${todo.id}">删除</button>
`;
listEl.appendChild(li);
});
}
function addTodo(text) {
todos.push({ id: nextId++, text, completed: false });
render();
}
addBtn.addEventListener('click', () => {
const text = inputEl.value.trim();
if (text) {
addTodo(text);
inputEl.value = '';
}
});
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const text = inputEl.value.trim();
if (text) {
addTodo(text);
inputEl.value = '';
}
}
});
listEl.addEventListener('click', (e) => {
const action = e.target.dataset.action;
const id = parseInt(e.target.dataset.id);
if (!action) return;
if (action === 'toggle') {
todos = todos.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
);
} else if (action === 'delete') {
todos = todos.filter(t => t.id !== id);
}
render();
});
</script>你学到了什么
你实现了一个完整的待办列表——这是前端最经典的练手项目。掌握了数据驱动渲染、事件委托、数组不可变更新(map/filter)等核心模式。这些模式在 React、Vue 等框架中同样适用。
相关资源
相关练习
- JS 函数预测题
预测函数执行结果。
相关工具
- JSON 格式化工具
待办数据用 JSON 存储。
相关参考文档
- JavaScript 数组方法
完整的 API 参考。
- JavaScript 对象
完整的 API 参考。
相关错误
- 无法读取 undefined 的属性
常见错误及解决方法。
- 引用错误:未定义
常见错误及解决方法。
相关术语
新手友好的术语表用大白话解释编程术语——变量、函数、DOM、Promise 等等。
相关错误
用大白话解释常见错误——什么意思、为什么发生、如何修复。
- 无法读取 undefined 的属性
常见错误及解决方法。
- 引用错误:未定义
常见错误及解决方法。