前端实时筛选适合≤1000行小数据,直接遍历DOM显隐元素最快;超量则需防抖(200ms)、统一事件监听、dataset取值、数值转换比较、语义化结构。

前端实时筛选适合小数据量(≤1000 行)
直接用 JavaScript 遍历 DOM 并显隐 <tr> 或 <code><div> 是最快落地的方式,但只适用于所有数据已加载到页面、且行数可控的场景。一旦超过 1000 行,输入抖动就会明显卡顿,<code>input 事件每触发一次就全量遍历,CPU 和重排压力都会上升。
关键实操点:
- 统一监听所有筛选控件的
input或change事件,共用一个filterTable()回调,避免多次遍历或状态覆盖 - 每个条件默认为
true(即“不限制”),空值不参与逻辑判断,防止漏行 —— 错误写法:if (name && dept) { ... };正确写法:const matchName = !nameVal || cellText.includes(nameVal); const matchDept = !deptVal || dataDept === deptVal; if (matchName && matchDept) { ... } - 优先读取
dataset属性(如data-category)而非textContent,避免因格式空格、HTML 标签或富文本干扰匹配 - 数值类条件必须用
parseFloat()或parseInt()转换再比较,字符串比较'200' 会返回 <code>true
后端驱动筛选才是真实业务的标配
只要数据来自 API、需分页、含权限控制或关联查询,就必须把筛选逻辑交给后端。前端只负责收集、校验、拼参并发起请求,别在浏览器里硬扛全量数据。
常见错误现象:
立即学习“前端免费学习笔记(深入)”;
- 用
document.querySelectorAll('[name]')手动遍历表单字段 —— 一旦加了动态渲染的筛选项(比如异步加载的部门下拉),name漏写或大小写不一致,整个参数就丢 - 把
name="brand[]"当通用写法 —— 这是 PHP 特有语法,FormData不识别方括号,后端收不到数组 - GET 请求拼接字符串手动加
encodeURIComponent—— 容易漏转义、出错,应直接用URLSearchParams或new FormData(form)
推荐做法:
- 整个筛选区包在
<form method="get"></form>里,name严格对齐后端接收字段名(如price_min、status) - 提交前用
const params = Object.fromEntries(new FormData(form).entries())一键提取干净键值对,空值自动被忽略 - 发请求时用
fetch(`/api/items?${new URLSearchParams(params)}`),安全、简洁、可书签
多条件“与”逻辑必须单次合并判断
不能对同一数据集反复 .filter(),也不能嵌套 if (a) { if (b) { if (c) { ... } } }。前者性能差、中间数组浪费内存;后者容易漏掉组合分支,比如某条件为空时逻辑跳过,导致本该显示的行被隐藏。
正确结构是:遍历每一项时,为每个非空条件单独计算布尔值,最后用 && 合并:
const rows = Array.from(document.querySelectorAll('#my-table tbody tr'));
rows.forEach(row => {
const nameMatch = !nameVal || row.cells[0].textContent?.trim().toLowerCase().includes(nameVal.toLowerCase());
const deptMatch = !deptVal || row.cells[1].dataset.dept === deptVal;
const priceMatch = !priceVal || parseFloat(row.cells[2].dataset.price) <= parseFloat(priceVal);
row.style.display = nameMatch && deptMatch && priceMatch ? '' : 'none';
});
注意点:
- 用
Array.from()把NodeList转成数组,避免边遍历边修改 DOM 引发索引错乱 -
dataset值天然小写,data-price-max对应row.dataset.pricemax,命名时避开驼峰和连字符歧义 - 文本匹配前统一
trim().toLowerCase(),避免大小写或首尾空格导致失败
防抖不是可选项,是必选项
用户敲字是连续行为,input 事件在中文输入法下还会高频触发(比如拼音选词过程)。不加防抖,每按一个键就执行一次全量过滤,移动端尤其卡顿,还可能触发竞态:后一次过滤还没完,前一次的 DOM 修改就被覆盖。
简单防抖实现(无依赖):
let filterTimer;
function onFilterChange() {
clearTimeout(filterTimer);
filterTimer = setTimeout(() => {
// 这里放真正的过滤逻辑
filterTable();
}, 200);
}
document.getElementById('filter-name').addEventListener('input', onFilterChange);
document.getElementById('filter-dept').addEventListener('change', onFilterChange);
容易被忽略的细节:
- 防抖要作用在整个筛选逻辑入口,不是每个输入框单独防抖 —— 否则改两个框仍会触发两次
- 防抖延迟设为 200ms 左右较平衡:短于 150ms 用户感知不到响应,长于 300ms 显得迟钝
- 点击“重置”按钮后,记得
clearTimeout(filterTimer),否则可能执行旧回调
<form></form>、乱起 name、靠 class 或 innerText 匹配,短期能跑,后期加个分页、同步 URL 参数或对接新后端字段,就得重写一半 JS。



















