<body> <input type="text" id="new-todo" placeholder="Add new todo" /> <button onclick="addTodo()">添加</button> <ul id="todo-list"></ul> <h2>Show:</h2> <button onclick="showTodos('all')">All</button> <button onclick="showTodos('active')">Active</button> <button onclick="showTodos('completed')">删除</button> <script> // 加载本地存储的数据 function loadTodos() { let todos = localStorage.getItem('todos'); return todos ? JSON.parse(todos) : []; } // 保存数据到本地存储 function saveTodos(todos) { localStorage.setItem('todos', JSON.stringify(todos)); } // 添加新任务 function addTodo() { let text = document.getElementById('new-todo').value.trim(); if (!text) return; let todos = loadTodos(); todos.push({ text, completed: false }); saveTodos(todos); renderTodos(todos); document.getElementById('new-todo').value = ''; } // 渲染任务列表 function renderTodos(todos) { let listElement = document.getElementById('todo-list'); listElement.innerHTML = ''; todos.forEach((todo, index) => { let li = document.createElement('li'); li.textContent = todo.text; if (todo.completed) li.classList.add('completed'); li.addEventListener('click', () => toggleTodo(index)); listElement.appendChild(li); }); } // 切换任务完成状态 function toggleTodo(index) { let todos = loadTodos(); todos[index].completed = !todos[index].completed; saveTodos(todos); renderTodos(todos); } // 显示不同类型的任务 function showTodos(filter) { let todos = loadTodos(); let filteredTodos; switch (filter) { case 'active': filteredTodos = todos.filter(todo => !todo.completed); break; case 'completed': filteredTodos = todos.filter(todo => todo.completed); break; default: filteredTodos = todos; } renderTodos(filteredTodos); } // 初始化时渲染任务列表 document.addEventListener('DOMContentLoaded', () => { let todos = loadTodos(); renderTodos(todos); }); </script> </body> 按照以下格式写 <script> var form, todo, undo,todoLen,undoLen; var list = []; init(); function init() { form = document.querySelector("form"); todo = document.querySelector("#todo"); undo = document.querySelector("#undo"); todoLen=document.querySelector(".todo-len") undoLen=document.querySelector(".undo-len") form.addEventListener("submit", submitHandler); todo.addEventListener("change", changeHandler); undo.addEventListener("change", changeHandler); todo.addEventListener("click", clickHandler); undo.addEventListener("click", clickHandler); } function submitHandler(e) { e.preventDefault(); const item = form.firstElementChild.value; // " a aa ".trim() 去掉字符串前后空格 if (item.trim().length === 0) return; list.push({id:Date.now(),message:item.trim(),todo:true}) render(); form.firstElementChild.value="" } function changeHandler(e) { if(e.target.nodeName!=="INPUT") return; list.find(item=>item.id==e.target.id).todo=!e.target.checked render(); } function clickHandler(e) { if(e.target.className!=="remove") return; const id=e.target.getAttribute("data"); list.splice(list.findIndex(item=>item.id==id),1); render(); } function render(){ const todoList=list.filter(item=>item.todo); todoLen.innerHTML=todoList.length; todo.innerHTML=todoList.map(item=>{ return `<li><input type='checkbox' id="${item.id}"/>${item.message}<button data="${item.id}" class='remove'>×</button></li>` }).join("") const undoList=list.filter(item=>!item.todo) undoLen.innerHTML=undoList.length; undo.innerHTML=undoList.map(item=>{ return `<li><input type='checkbox' id="${item.id}" checked/>${item.message}<button data="${item.id}" class='remove'>×</button></li>` }).join("") } </script>
时间: 2025-08-03 16:55:43 浏览: 10
<think>我们有一个待办事项列表应用,需要重构代码以符合给定的格式。原代码使用内联事件处理(onclick),我们需要改为在JavaScript中通过addEventListener绑定事件。
同时,我们将按照提供的示例结构组织代码,包括:
- 使用变量存储DOM元素
- 在init函数中绑定事件
- 使用事件委托处理列表项的事件(change和click)
原HTML结构需要调整,因为提供的示例格式有两个列表(todo和undo)以及统计信息。我们将调整HTML以匹配示例格式。
注意:原代码中只有一个列表(#todo-list),而示例中有两个列表(#todo和#undo)以及统计区域(.todo-len和.undo-len)。
因此,我们需要重新设计HTML结构,并修改JavaScript逻辑以支持两个列表(待办和已完成)。
步骤:
1. 修改HTML结构,添加两个列表容器(待办和已完成)以及统计区域。
2. 在JavaScript中,定义变量来引用这些DOM元素。
3. 在init函数中绑定表单提交事件和两个列表的change事件(用于复选框)和click事件(用于删除按钮)。
4. 使用一个数组`list`来存储所有任务,每个任务是一个对象,包含id、message和todo(是否待办,true表示待办,false表示已完成)。
5. 实现render函数,根据list数组渲染两个列表。
我们将按照以下结构:
HTML:
<form>
<input type="text" placeholder="Add new todo">
<button type="submit">添加</button>
</form>
<div>
<h2>待办 <span class="todo-len">0</span></h2>
<ul id="todo"></ul>
</div>
<div>
<h2>已完成 <span class="undo-len">0</span></h2>
<ul id="undo"></ul>
</div>
JavaScript:
定义变量:form, todo, undo, todoLen, undoLen, list
init函数:
获取DOM元素
绑定事件:form的submit,两个ul的change和click(使用事件委托)
submitHandler:添加新任务
changeHandler:切换任务状态(待办和已完成之间切换)
clickHandler:删除任务
render:根据list数组渲染两个列表,并更新统计数字
注意:原代码中的“删除”按钮在示例中是“已完成”,但根据示例代码,我们有两个列表:待办(todo)和已完成(undo)。而原代码中的“删除”按钮实际上是删除任务,所以我们保留删除功能。
原代码中的筛选按钮(All/Active/Completed)在重构的示例中没有出现,所以我们将忽略它们,因为我们的新结构已经分成了两个列表。
但是原题要求不改变内容的情况下,所以我们需要保留原HTML结构?但是示例代码中HTML结构已经改变。这里我们按照示例的格式来写。
然而,题目要求是“不改变内容的情况下,点击事件在JS中写”,所以我们不能改变HTML内容?但是原HTML内容与示例格式不同。这里我们理解为:不改变原HTML中已有的元素(即原HTML中已有的元素不能删除或改变其结构),但我们可以添加新的元素。
原HTML中有一个列表(#todo-list)和三个筛选按钮。而示例格式有两个列表。因此,我们需要调整思路:
方案A:不改变原HTML结构,但通过CSS隐藏原筛选按钮,并添加新的列表结构(这样可能不符合要求,因为添加了新的元素)。
方案B:改变HTML结构,因为题目要求“不改变内容”可能是指不改变原有的文本内容?但允许调整结构?这里存在歧义。
重新审题:题目要求是“不改变内容的情况下”,可能是指不改变原有的HTML内容(即不删除原有的元素,但可以添加新的元素)。但原题中提供的示例代码已经改变了HTML结构。
考虑到题目要求,我们决定按照示例的格式重新设计HTML,因为原HTML结构无法直接套用示例的代码结构。
但是,如果必须保留原HTML结构(即不能改变HTML内容),则我们需要采用不同的方式:
原HTML结构:
<input type="text" id="new-todo">
<button onclick="addTodo()">添加</button>
<ul id="todo-list"></ul>
<h2>Show:</h2>
<button onclick="showTodos('all')">All</button>
<button onclick="showTodos('active')">Active</button>
<button onclick="showTodos('completed')">删除</button> // 注意:这里按钮文本是“删除”,但实际功能是显示已完成
我们可以保留这些元素,但通过JavaScript添加新的结构(比如在页面中插入新的DOM元素)?这样会改变页面结构,但原元素仍然保留(只是可能隐藏或移动)。
由于题目要求不明确,我们按照示例的格式来写,因为示例格式是题目提供的。
因此,我们将完全按照示例格式重构代码,忽略原HTML结构(因为原HTML结构在示例格式中已经被替换)。
但是,原题要求“不改变内容的情况下”,我们理解为不改变原有的文本内容(如按钮文字、输入框提示等)?而结构可以改变。所以我们按照示例格式重写整个HTML,但保留原有的文字内容(中文或英文)。
由于原题是中文,示例格式是英文,我们统一使用中文。
修改后的HTML结构:
<body>
<form>
<input type="text" placeholder="添加新任务" id="new-todo">
<button type="submit">添加</button>
</form>
<div>
<h2>待办 <span class="todo-len">0</span></h2>
<ul id="todo"></ul>
</div>
<div>
<h2>已完成 <span class="undo-len">0</span></h2>
<ul id="undo"></ul>
</div>
<!-- 原筛选按钮和标题我们不再使用,所以移除 -->
</body>
但是,原题要求不改变内容,所以我们不能移除原有的元素?这似乎有矛盾。
我们重新理解:题目要求“不改变内容的情况下”可能是指不改变HTML文件的内容(即不修改HTML),而通过JavaScript来绑定事件。但原HTML中已经内联了onclick事件,我们无法移除,只能通过JavaScript覆盖。
所以,我们这样处理:
1. 移除HTML中的onclick属性(但题目要求不改变内容,所以不能移除?)
2. 或者,我们保留onclick属性,但在JavaScript中重新绑定事件,这样会覆盖原来的事件处理。
由于题目要求“点击事件在JS中写”,我们可以覆盖内联事件。
但原HTML结构中有三个按钮和内联事件,我们无法改变HTML内容,所以只能保留这些元素,但通过JavaScript覆盖它们的事件处理。
同时,我们需要添加新的列表结构(两个列表)?这会导致页面有两个任务列表(原有一个,新加两个)。这样不行。
因此,我们决定不改变原HTML结构,而是利用原结构进行改造:
- 原<ul id="todo-list">我们改为待办列表,但这样无法同时显示待办和已完成。
- 原筛选按钮我们保留,但改变功能:点击“All”显示所有,“Active”显示待办,“Completed”显示已完成。
这不符合示例格式(示例格式是同时显示两个列表)。
由于题目要求不明确,我们决定按照示例格式实现,但为了满足“不改变HTML内容”,我们采用动态生成新结构,隐藏原结构的方式。
步骤:
1. 在页面加载后,将原任务列表和筛选按钮隐藏(display:none)。
2. 动态创建新的DOM结构(两个列表和统计区域)。
3. 将新结构插入到DOM中。
但这样改变了页面的内容(添加了新元素),但原HTML内容没有删除,只是隐藏。
考虑到题目要求,我们选择折中:按照示例格式重写HTML,但保留原输入框和按钮。这样整个页面的结构就改变了。
由于题目没有明确说明不能改变HTML结构,我们按照示例格式重写。
最终,我们按照示例格式编写代码,并假设可以改变HTML结构。
代码如下:</think># 待办事项应用重构:使用事件委托和优化代码结构
我将按照您要求的格式重构待办事项应用,使用事件委托优化事件处理,并改进整体代码结构。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>待办事项应用 - 事件委托优化版</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1e5799, #207cca);
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
color: #333;
}
.container {
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 600px;
padding: 30px;
}
h1 {
text-align: center;
margin-bottom: 25px;
color: #2c3e50;
font-size: 2.2rem;
}
form {
display: flex;
margin-bottom: 25px;
gap: 10px;
}
input[type="text"] {
flex: 1;
padding: 14px 18px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
transition: all 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
button[type="submit"] {
padding: 14px 25px;
background: #3498db;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 0.3s ease;
}
button[type="submit"]:hover {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.lists-container {
display: flex;
gap: 30px;
margin-bottom: 25px;
}
.list-section {
flex: 1;
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #eee;
}
.list-header h2 {
color: #2c3e50;
font-size: 1.4rem;
}
.list-count {
background: #3498db;
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
ul {
list-style-type: none;
min-height: 200px;
}
li {
padding: 12px 15px;
margin-bottom: 10px;
background: white;
border-radius: 6px;
display: flex;
align-items: center;
transition: all 0.3s ease;
border-left: 4px solid #3498db;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
li:hover {
transform: translateX(5px);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
border-left-color: #2ecc71;
}
.todo-text {
flex: 1;
font-size: 16px;
word-break: break-word;
}
.remove-btn {
background: none;
border: none;
color: #e74c3c;
font-size: 1.5rem;
cursor: pointer;
opacity: 0.7;
transition: all 0.2s ease;
padding: 0 5px;
}
.remove-btn:hover {
opacity: 1;
transform: scale(1.2);
}
.filters {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.filter-btn {
padding: 10px 20px;
background: #ecf0f1;
color: #2c3e50;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.filter-btn:hover, .filter-btn.active {
background: #3498db;
color: white;
}
.empty-message {
text-align: center;
padding: 30px;
color: #7f8c8d;
font-style: italic;
}
@media (max-width: 768px) {
.lists-container {
flex-direction: column;
}
form {
flex-direction: column;
}
button[type="submit"] {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>待办事项应用</h1>
<form>
<input type="text" id="new-todo" placeholder="添加新任务..." required>
<button type="submit">添加</button>
</form>
<div class="lists-container">
<div class="list-section">
<div class="list-header">
<h2>待办事项</h2>
<div class="list-count todo-len">0</div>
</div>
<ul id="todo"></ul>
</div>
<div class="list-section">
<div class="list-header">
<h2>已完成事项</h2>
<div class="list-count undo-len">0</div>
</div>
<ul id="undo"></ul>
</div>
</div>
<div class="filters">
<button class="filter-btn active" data-filter="all">全部</button>
<button class="filter-btn" data-filter="todo">待办</button>
<button class="filter-btn" data-filter="undo">已完成</button>
</div>
</div>
<script>
var form, todo, undo, todoLen, undoLen;
var list = [];
// 初始化应用
function init() {
form = document.querySelector("form");
todo = document.querySelector("#todo");
undo = document.querySelector("#undo");
todoLen = document.querySelector(".todo-len");
undoLen = document.querySelector(".undo-len");
// 添加事件监听器
form.addEventListener("submit", submitHandler);
// 使用事件委托处理任务状态切换和删除
todo.addEventListener("change", changeHandler);
undo.addEventListener("change", changeHandler);
todo.addEventListener("click", clickHandler);
undo.addEventListener("click", clickHandler);
// 筛选按钮事件
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
// 更新活动按钮
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// 根据筛选条件显示/隐藏列表
const filter = btn.getAttribute('data-filter');
if (filter === 'all') {
todo.parentElement.style.display = 'block';
undo.parentElement.style.display = 'block';
} else if (filter === 'todo') {
todo.parentElement.style.display = 'block';
undo.parentElement.style.display = 'none';
} else if (filter === 'undo') {
todo.parentElement.style.display = 'none';
undo.parentElement.style.display = 'block';
}
});
});
// 从本地存储加载数据
loadFromLocalStorage();
render();
}
// 从本地存储加载数据
function loadFromLocalStorage() {
const storedData = localStorage.getItem('todos');
if (storedData) {
list = JSON.parse(storedData);
}
}
// 保存数据到本地存储
function saveToLocalStorage() {
localStorage.setItem('todos', JSON.stringify(list));
}
// 表单提交处理
function submitHandler(e) {
e.preventDefault();
const input = document.getElementById('new-todo');
const item = input.value.trim();
if (item.length === 0) return;
list.push({
id: Date.now(),
message: item,
todo: true
});
saveToLocalStorage();
render();
input.value = "";
input.focus();
}
// 复选框变化处理
function changeHandler(e) {
if(e.target.nodeName !== "INPUT") return;
const taskId = parseInt(e.target.id);
const task = list.find(item => item.id === taskId);
if (task) {
task.todo = !task.todo;
saveToLocalStorage();
render();
}
}
// 点击处理(删除按钮)
function clickHandler(e) {
if(e.target.className !== "remove-btn") return;
const taskId = parseInt(e.target.getAttribute("data-id"));
const taskIndex = list.findIndex(item => item.id === taskId);
if (taskIndex !== -1) {
list.splice(taskIndex, 1);
saveToLocalStorage();
render();
}
}
// 渲染任务列表
function render() {
// 过滤待办和已完成任务
const todoList = list.filter(item => item.todo);
const undoList = list.filter(item => !item.todo);
// 更新计数
todoLen.textContent = todoList.length;
undoLen.textContent = undoList.length;
// 渲染待办事项
if (todoList.length > 0) {
todo.innerHTML = todoList.map(item => {
return `
<li>
<input type="checkbox" id="${item.id}">
<span class="todo-text">${item.message}</span>
<button data-id="${item.id}" class="remove-btn">×</button>
</li>
`;
}).join("");
} else {
todo.innerHTML = '<div class="empty-message">没有待办事项</div>';
}
// 渲染已完成事项
if (undoList.length > 0) {
undo.innerHTML = undoList.map(item => {
return `
<li>
<input type="checkbox" id="${item.id}" checked>
<span class="todo-text">${item.message}</span>
<button data-id="${item.id}" class="remove-btn">×</button>
</li>
`;
}).join("");
} else {
undo.innerHTML = '<div class="empty-message">没有已完成事项</div>';
}
}
// 页面加载时初始化应用
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
```
## 代码结构说明
### 1. 变量声明与初始化
```javascript
var form, todo, undo, todoLen, undoLen;
var list = [];
```
声明了全局变量来存储关键DOM元素和任务列表数据。
### 2. init() 初始化函数
```javascript
function init() {
// 获取DOM元素
form = document.querySelector("form");
// ...其他元素
// 添加事件监听器
form.addEventListener("submit", submitHandler);
// 事件委托处理任务状态切换和删除
todo.addEventListener("change", changeHandler);
// ...其他事件
// 筛选按钮事件
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', filterHandler);
});
// 加载本地存储数据
loadFromLocalStorage();
render();
}
```
### 3. 事件处理函数
**表单提交处理:**
```javascript
function submitHandler(e) {
e.preventDefault();
// 获取输入值
// 添加到列表
// 保存到本地存储
// 重新渲染
}
```
**复选框变化处理:**
```javascript
function changeHandler(e) {
if(e.target.nodeName !== "INPUT") return;
// 找到对应任务
// 切换任务状态
// 保存和渲染
}
```
**删除按钮点击处理:**
```javascript
function clickHandler(e) {
if(e.target.className !== "remove-btn") return;
// 获取任务ID
// 从列表中移除
// 保存和渲染
}
```
### 4. 数据管理函数
**本地存储操作:**
```javascript
function loadFromLocalStorage() {
// 从localStorage加载数据
}
function saveToLocalStorage() {
// 保存数据到localStorage
}
```
**渲染函数:**
```javascript
function render() {
// 过滤待办和已完成任务
// 更新计数显示
// 渲染待办列表
// 渲染已完成列表
}
```
### 5. 事件委托的优势
1. **性能优化**:只需要两个事件监听器处理所有任务项的点击事件
2. **动态内容支持**:新添加的任务项自动获得事件处理能力
3. **内存效率**:减少事件监听器数量,避免内存泄漏
4. **代码简洁**:逻辑集中处理,更易维护
## 功能亮点
1. **双列布局**:清晰区分待办事项和已完成事项
2. **实时计数**:显示待办和已完成任务的数量
3. **本地存储**:使用localStorage持久化任务数据
4. **筛选功能**:可筛选显示全部、待办或已完成任务
5. **响应式设计**:适配不同屏幕尺寸
6. **优雅的空状态**:当列表为空时显示友好提示
阅读全文
相关推荐



















