防抖必须加且延迟设为300ms,用setTimeout+clearTimeout实现;筛选逻辑应在数据层用filter()处理,多选状态用Set存储;渲染用template+DocumentFragment避免innerHTML,大数据量改用显隐控制而非增删DOM。

防抖必须加,且延迟要合理
不加防抖的 input 事件一打字就触发筛选,用户还没松手,DOM 已重绘三四次。300ms 是多数场景下的平衡点:够覆盖连续输入间隙,又不至于让用户觉得卡顿。
用 setTimeout + clearTimeout 就够,别依赖第三方库:
let filterTimer;
input.addEventListener('input', () => {
clearTimeout(filterTimer);
filterTimer = setTimeout(() => filterData(), 300);
});
- 移动端键盘回车、iOS 搜索框清空按钮也会触发
input,但此时值可能是空字符串——得先trim()再判断长度,否则一清空就闪退 - 多个输入框共用同一个防抖 timer,避免竞态:不能每个框都自己起一个
setTimeout
筛选逻辑必须跑在数据层,不是 DOM 层
直接遍历 document.querySelectorAll('tbody tr') 看单元格文本?2000 行以上就会掉帧。浏览器每次读取 row.cells[0].textContent 都可能触发重排,性能随行数线性恶化。
正确做法是缓存原始数据数组,所有条件统一走 filter() 计算,再批量渲染结果:
const filtered = originalData.filter(item => (!category || item.category === category) && (!keyword || item.name.toLowerCase().includes(keyword.toLowerCase())) && (selectedStatus.has(item.status) || selectedStatus.size === 0) );
- 多选状态(如 checkbox 或
)用Set存值,selectedStatus.has()比Array.includes()快一个数量级 - 正则匹配关键词时,若需忽略大小写,提前缓存
new RegExp(keyword, 'i'),但 keyword 为空或含.*+等特殊字符时必须先escape
渲染阶段别碰 innerHTML,改用 template + DocumentFragment
每次筛选后写 tbody.innerHTML = htmlString,等于把整张表 DOM 全销毁重建:事件丢失、焦点消失、XSS 风险、2000 行时单次重绘超 16ms。
用 预定义结构,克隆后填值:
<template id="row-template"><tr> <td class="name"></td> <td class="status"></td> </tr></template>
- 克隆后用
row.querySelector('.name').textContent = item.name填值,不拼字符串,杜绝 XSS - 有交互的节点(如编辑按钮),必须在克隆后立刻绑定事件,不能靠事件委托——
内容不在主 DOM 树里 - ID 冲突要处理:
clone.querySelectorAll('[id]').forEach(el => el.id += '-'+Math.random().toString(36).substr(2, 9))
大数据量时显隐控制比增删 DOM 更快
上万行?别筛了,改用「固定容器高度 + overflow-y: auto + 行级 display: none」。比批量插入/删除节点快得多,也不丢滚动位置和焦点。
- 初始加载时所有
tr设style.display = 'none',筛选后只设匹配行style.display = '' - 空搜索词时,别漏掉恢复全部显示:
rows.forEach(row => row.style.display = '') - 虚拟滚动是最后手段:真到几万行,就得只渲染可视区域的
tr,配合scrollTop动态计算偏移
实际项目里最容易被跳过的,是「空值默认放行」这个逻辑。每个条件都得写成 !val || condition,而不是 val && condition,否则任意一个筛选框为空,整条数据就被干掉了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











