📝 项目目标:
创建一个待办事项输入和筛选功能,可以输入多个任务,实时筛选出匹配的内容。
✅ 功能拆解
-
用户可添加任务到列表中
-
用户可以通过输入关键词进行筛选
-
用户点击删除按钮可以移除对应的任务
-
若没有匹配项,显示提示信息“没有匹配的任务”
💡 你将练习到的重点知识
-
input
的input
事件(实时监听输入) -
数组的
filter()
方法 -
DOM 动态渲染基础
-
小模块化思维
-
掌握了事件委托的基本原理和使用
-
理解
dataset
与类型转换在前端中的应用
HTML 部分结构 (可自己定义):
<input type="text" id="todoInput" placeholder="添加任务">
<button id="addBtn">添加</button>
<input type="text" id="filterInput" placeholder="筛选任务">
<ul id="todoList"></ul>
实践代码如下:
const todoInput = document.getElementById('todoInput')
const addBtn = document.getElementById('addBtn')
const filterInput = document.getElementById('filterInput')
const todoList = document.getElementById('todoList')
let taskList = []
renderList = (keyword = '') => {
// 清空原有列表
todoList.innerHTML = ''
// 使用 filter 进行筛选(注意保留原始 taskList)
const filterList = taskList.filter((data) => {
return data.toLowerCase().includes(keyword.toLowerCase())
})
if (filterList.length === 0) {
const li = document.createElement('li')
li.textContent = '没有匹配的任务'
li.style.color = 'gray'
todoList.appendChild(li)
return
}
// 遍历筛选结果,创建 <li> 并添加到 todoList 中
filterList.forEach((element, index) => {
const li = document.createElement('li')
li.textContent = element
const delBtn = document.createElement('button')
delBtn.textContent = '❌'
// 给每个任务绑定索引 data-index
delBtn.setAttribute('data-index', taskList.indexOf(element))
li.appendChild(delBtn)
todoList.appendChild(li)
});
}
addBtn.addEventListener('click', () => {
// 获取输入,判断非空,添加到 taskList 中,并清空输入框
const text = todoInput.value.trim()
if (text) {
taskList.push(text)
}
todoInput.value = ''
// 调用 renderList 渲染当前任务列表(可传当前 filterInput.value)
renderList(filterInput.value)
})
filterInput.addEventListener('input', () => {
// 监听输入变化,调用 renderList 进行筛选显示
renderList(filterInput.value)
})
// 绑定删除事件(事件委托)
todoList.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
const index = Number(e.target.dataset.index)
if (index !== undefined && !isNaN(index))
taskList.splice(index, 1)
renderList(filterInput.value)
}
})
页面效果展示 :
❗遇到的问题和解决过程
🧩 问题1:为什么需要index !== undefined ?
if (index !== undefined)
这一句是一个防御性写法,意思是:确保我们拿到了有效的 data-index
才继续执行删除逻辑。
但你可能会问:“我们不是每个按钮都加了 data-index
吗?那什么时候会是 undefined 呢?”
什么时候可能出现 index === undefined
?
-
点击的不是你渲染的删除按钮
-
比如将来你可能在
todoList
里加了别的按钮(比如“编辑”按钮),但没设置data-index
。 -
用户点了那个按钮,
e.target.dataset.index
就会是undefined
。
-
-
你手滑忘了加
data-index
-
假设你在某次更新中遗漏给按钮加
data-index
属性,点这个按钮也会导致undefined
。
-
-
开发或调试时 DOM 改变了
-
有时 DOM 结构可能被意外改动,比如清空列表但按钮没移除,残留元素可能没
data-index
。
-
🧠 总结一句话:
虽然现在看起来永远都会有
data-index
,但写这个判断,是为了避免将来出错导致程序崩溃,是一种编写健壮代码的习惯。
🧩 问题2:为什么需要 !isNaN(index)
?
-
dataset.index
是字符串,如"2"
-
Number("2")
会变成数字2
-
但如果是
"abc"
或undefined
会变成NaN
,所以要判断isNaN
🧩 问题3: 为什么要用 taskList.indexOf(element)
而不是 index
?
-
filterList
是基于关键词筛选出来的任务子集,是taskList
的“视图”副本。 -
filterList
中的元素索引与taskList
中的索引不一定对应。 - 删除操作必须针对原始数组
taskList
进行,而非filterList
。 - 因此,删除按钮的索引必须是
taskList
中对应任务的真实索引,避免删除错误的任务。