必须用 input 事件监听所有筛选框,配合防抖、统一读值和短路逻辑判断,以确保多条件实时稳定过滤;change 仅失焦触发,体验卡顿,input 覆盖键盘、粘贴、语音、IME、移动端搜索等全部输入场景。

直接用 input 事件监听所有筛选框,配合防抖 + 单次统一读取 + 短路逻辑判断,就能稳定响应多条件变化。别用 change 或 keyup,也别给每个框写独立回调。
为什么必须用 input 事件而不是 change
change 只在失焦时触发,用户连续修改多个字段后点空白处才执行一次过滤,体验卡顿且不可预测;input 覆盖所有输入场景:键盘敲击、粘贴、语音输入、IME 上屏、移动端搜索键(type="search" 时更可靠)。
- IE11 不支持
input?可 fallback 到propertychange,但仅限必要时启用 - 移动端部分安卓输入法对
type="text"的回车键不触发input,换成type="search"即可解决 - 别在回调里直接操作 DOM —— 防抖延迟期间用户可能已改了多个字段,只应读值,不立即渲染
怎么避免多个输入框互相覆盖结果
每个 input 或 select 单独绑事件没问题,但回调函数必须共用一个,且只做「读值 + 防抖触发过滤」,不能各自执行完整过滤逻辑。
- 错误做法:
nameInput.addEventListener('input', filterByName)和deptSelect.addEventListener('change', filterByDept)—— 后触发的会覆盖前一次状态 - 正确做法:所有控件共用
onFilterChange(),内部一次性读取form.elements或new FormData(form) - 用
setTimeout+clearTimeout实现 200ms 防抖,避免高频重绘(尤其行数 > 300 时)
过滤逻辑怎么写才不漏行、不错显
必须用单次遍历 + 所有条件用 && 连接,每个条件默认返回 true(空值不参与限制),而不是链式 .filter().filter() 或嵌套 if。
- 错误示例:
rows.filter(byName).filter(byDept)—— 第一次过滤掉的行,第二次根本没机会判断 - 正确结构:
const show = (!nameVal || row.cells[0].textContent.includes(nameVal)) && (!deptVal || row.cells[1].dataset.dept === deptVal) - 数值字段要先转字符串:
row.cells[2].textContent?.toString().includes(priceMinVal),否则null.includes()报错 - 精确匹配建议清洗后再比:
cleanText(row.cells[0].textContent) === cleanText(keyword),避免空格、全角字符干扰
DOM 操作容易踩的坑
别反复设 style.display,也别依赖 CSS 类切换 —— 内联样式优先级高,类名可能被锁死;清空搜索时必须显式恢复所有行显示状态。
- 遍历范围必须是
document.querySelectorAll('tbody tr'),不是'tr',否则表头thead tr会被误隐藏 - 无结果时插入提示行,用
colspan动态取自thead th数量,别硬编码 - 插入前先
remove()已存在的.no-results行,避免重复 - 不要用
visibility: hidden或opacity: 0做隐藏,它们仍占布局空间,会导致表格错位
最易被忽略的是数据源和 UI 状态不同步:导出按钮、分页总数、统计文案这些地方,必须基于当前过滤后的数组计算,而不是原始 DOM 或初始数据 —— 否则用户点了“导出”,实际导出的是全部 10000 条,不是当前看到的 3 条。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











