必须使用 input 事件监听搜索,它在输入、粘贴、删除、中文上屏时均触发;清洗输入值需 trim()、压缩空白、转小写;匹配用 textContent;多关键词用 split().filter();显隐用 classList.toggle("hidden")。

用 input 事件监听搜索,不是 keyup 或 change
必须用 input 事件——它在每次输入、粘贴、删除、中文上屏时都触发,覆盖所有真实编辑行为。keyup 在拼音未选字时就上报“shu”,搜不到“书”;change 要等失焦,根本没法实时过滤。
注意:input 不冒泡,但你直接绑定在 <input> 上就行,不用委托;iOS Safari 有轻微延迟,加 setTimeout 防抖只是可选优化,不是必须。
- 别写
searchInput.addEventListener('keyup', ...) - 别写
searchInput.addEventListener('change', ...) - 正确写法:
searchInput.addEventListener('input', handleFilter)
匹配前清洗输入值,避免空格和大小写干扰
用户输 “ React ” 或 “react ”(中文空格),不处理就会匹配失败。只用 toLowerCase() 不够,得先清洗。
正确清洗方式:const keyword = searchInput.value.trim().replace(/\s+/g, ' ').toLowerCase()。这里 \s 匹配所有空白符(含全角空格、换行、制表符),replace(/\s+/g, ' ') 把连续空白压缩成单个英文空格,再 trim() 去首尾,最后转小写。
立即学习“前端免费学习笔记(深入)”;
- 空字符串或纯空格时,应显示全部列表项,不要直接 return
- 匹配字段必须取
textContent,不是innerHTML——否则带 HTML 标签的数据(如 Vue 渲染的)会把标签当文本匹配 - 多关键词支持:用
keyword.split(' ').filter(Boolean)拆出有效词,每个都includes(),别拼正则,免得漏转义[.*+?^${}()|[\]]
隐藏不匹配项,别硬设 style.display
用 element.classList.toggle("hidden") 控制显隐最稳妥。硬写 item.style.display = "none" 会覆盖元素原本的 display 类型(比如 inline-flex 变成 block),导致布局错位。
CSS 中定义:.hidden { display: none; } 即可。如果需要过渡动画或保留占位,可用 .hidden { opacity: 0; visibility: hidden; pointer-events: none; }。
- 重置时别遍历 DOM 再一个个删
style.display,直接document.querySelectorAll(".item").forEach(el => el.classList.remove("hidden")) - 别给
.hidden加!important,否则和组件库样式冲突难调试 - 大数据量(超 500 条)时,DOM 重排才是卡顿主因,不是事件频率——先把所有判断做完,再统一
toggle,比防抖更治本
list 属性 + datalist 不等于搜索过滤
<input list="xxx"> 和 <datalist id="xxx"> 只提供原生下拉联想,不支持实时过滤列表项。它只做前缀匹配(少数新版 Chrome 支持子串),大小写行为不统一,且无法高亮、无法模糊、不能响应拼音输入。
常见静默失败点:list 值必须和 datalist 的 id 字符级完全一致(大小写、空格、符号均敏感);option 必须有非空 value,否则不参与匹配;type 不能是 number 或 date,否则联想失效。
- 想实现“输‘bj’出‘Beijing’”,必须放弃
datalist,改用input事件 + JS 动态渲染自定义下拉 - 移动端 iOS Safari 对
datalist支持极差,部分机型根本不显示选项 - 样式完全不可控,也不能加图标、分组或点击回调
list 和 id 差一个大小写,或者 option 少了 value,浏览器一声不吭,列表就是不出来。



















