必须监听 input 事件,因其覆盖键盘输入、粘贴、自动填充、语音输入及系统文本替换等所有输入场景;keyup 漏粘贴和填充,change 仅失焦触发,80% 搜索失效源于错误绑定。

input 事件必须用,不能用 keyup 或 change,否则粘贴、自动填充、语音输入都会失效。
为什么必须监听 input 事件?
用户敲键盘、粘贴文本、触发浏览器自动填充、甚至用 macOS 的“文本替换”或 iOS 的 QuickType,都会触发 input 事件;而 keyup 漏掉粘贴和自动填充,change 只在失焦时触发,中间过程完全不可见。实测中,80% 的搜索失灵都源于用了 onkeyup 或直接写内联 onchange。
- 绑定方式必须是
searchInput.addEventListener('input', handler),别写oninput="handler()"—— 后者无法传参、难调试、且和防抖不兼容 - IE11 不支持
input?加 fallback:searchInput.addEventListener('propertychange', handler)(仅 IE) - 防抖不是可选,是刚需:500 行以上表格,不加
setTimeout或debounce,连续输入会卡顿明显;推荐延迟 200ms
DOM 遍历必须显式限定 tbody
别信浏览器自动补全 tbody。Safari 旧版、部分 Android WebView 会跳过生成,导致 querySelectorAll('tr') 拿不到数据行,或把 thead 也混进来。必须在 HTML 中手写 <tbody>,JS 里只查 <code>table.querySelectorAll('tbody tr')。
- 跳过空行:
if (!row.cells.length) continue,避免row.cells[0]报undefined - 别用
getElementsByTagName('tr')—— 它返回所有tr,包括表头、脚注、嵌套表格里的行 - 500+ 行时,提前缓存单元格文本:
const rowData = Array.from(rows, r => Array.from(r.cells).map(c => c.textContent?.trim() || '')),后续过滤只操作数组,不反复读 DOM
多列模糊匹配的正确写法
只比第一列是典型翻车点。用户搜“北京”,结果姓名列没“北京”就显示“未找到”,实际地址列有。正确做法是:每行遍历所有 td,任一单元格匹配即显示整行。
- 大小写统一处理一次:
const keyword = input.value.trim().toLowerCase(),之后每个单元格都转小写再includes() - 数值或 null 要兜底:
(cell.textContent || '').toString().toLowerCase().includes(keyword),防止123.includes('2')报错 - 空搜索恢复全部:
if (!keyword) { rows.forEach(r => r.style.display = '') },别漏这句,否则清空后表格空白 - 隐藏用
row.style.display = 'none',恢复用row.style.display = ''(不是'table-row'),否则某些 CSS 重置后失效
无结果提示和状态同步最容易被忽略
只更新 tbody 内容,却不改页码、总条数、导出按钮的数据源,是线上最常被投诉的问题。用户导出“当前筛选结果”,结果导出的是原始全部数据。
- 无匹配时,插入一行
<tr><td colspan="4">未找到包含“关键词”的记录</td></tr>,colspan值必须动态读取thead th数量,不能硬编码 - 过滤后立刻更新:
document.getElementById('total-count').textContent = filteredRows.length - 导出功能的数据源必须指向当前
filteredData数组,而不是原始rawData - 如果用了分页,
currentPage = 1要重置,否则第二页可能显示空内容
tbody 缺失、以及多列逻辑没展开这三处。写完先拿粘贴测试一遍,再删光重输,比跑单元测试还管用。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











