精确匹配应单次遍历+各条件独立判断并默认true,清洗文本后比对,共用防抖过滤回调,空输入必须返回true。

多条件精确匹配不是靠堆 filter() 或写一堆 if 嵌套实现的,核心在于:每个条件独立判断、默认放行、最终用 && 合并布尔结果。漏掉“默认 true”或误用 includes() 代替 ===,都会导致筛选错乱或空结果。
为什么链式 filter() 会导致漏行
链式调用如 rows.filter(...).filter(...) 会创建中间数组,第二轮 filter() 只在第一轮结果上运行——如果某行被第一个条件筛掉,后面条件根本没机会判断它。这违背了“所有条件同时生效”的语义。
正确做法是单次遍历 + 每个条件独立计算:
const deptMatch = deptInput.value ? cleanCell(row.cells[2]) === cleanKeyword(deptInput.value) : trueconst roleMatch = roleInput.value ? cleanCell(row.cells[3]) === cleanKeyword(roleInput.value) : true- 最终统一判断:
if (deptMatch && roleMatch) { tr.style.display = ''; }
精确匹配必须清洗文本再比对
直接用 textContent.trim() === input.value.trim() 仍可能失败:单元格里有全角空格、连续空格、不可见 Unicode 字符(如 \uFEFF),用户输入也可能带空格。不清洗就等于把“北京”和“北京 ”当两个不同值。
立即学习“前端免费学习笔记(深入)”;
清洗函数要统一处理两端和中间:
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
const clean = s => s?.trim().replace(/[\s\uFEFF\xA0]+/g, ' ');
然后所有比对都基于清洗后结果:clean(row.cells[2].textContent) === clean(deptInput.value)。别忘了 data-value 属性更可靠——后端传枚举值时,前端应优先读 row.cells[2].getAttribute('data-value') 而非显示文本。
多个输入框共用一个过滤回调,否则状态会打架
给每个 <input> 单独绑 input 事件,再各自执行一次完整过滤,会出现竞态:用户快速改部门+角色,后触发的那个回调会覆盖前一个的结果,看起来像“只响应最后一次输入”。
必须集中读取、统一执行:
- 所有输入框共用同一个
onFilterChange()回调 - 回调内一次性读取:
const dept = deptInput.value; const role = roleInput.value; - 防抖用
setTimeout包一层,但 clearTimeout 必须作用于同一个 timer 变量 - 只操作
tbody tr,不碰thead或tfoot,避免结构错位
精确匹配下“空输入”必须返回 true
这是最容易被忽略的一点。如果写成 deptInput.value && clean(...) === clean(...),那只要部门输入框为空,整个表达式就是 false,整行被隐藏——哪怕角色匹配也没用。这等于把“不限部门”变成了“部门必须为空”。
每个条件必须显式处理空值:
const matchesDept = deptInput.value ? clean(row.cells[2].textContent) === clean(deptInput.value) : true;
注意是 : true,不是省略。多个条件之间用 && 连接,才能保证“任一条件未填写即视为不限制”。


















