必须用 + 而非 列表,因其具备语义化、可访问性(屏幕阅读器准确播报)、SEO 权重识别及移动端滑动支持;实时搜索需用 input 事件监听、e.target.value.trim() 取值、批量 DOM 操作、XSS 安全高亮及语义化空状态处理。

直接用 <ul></ul> 包裹搜索结果,别用 <div> 堆砌——否则屏幕阅读器读不出列表语义,SEO 也抓不到标题层级,键盘导航会跳得乱。
<h3>为什么必须用 <code><ul></ul> + <li> 而不是 <div> 列表
<p>浏览器对 <code><ul></ul> 有默认语义和可访问性支持:<ul></ul> 自动被识别为“列表”,<li> 是“列表项”,NVDA、VoiceOver 等工具能准确播报“共 X 项,当前第 Y 项”;而一堆 <div class="result"> 在辅助技术眼里只是普通容器,毫无结构信息。
<ul><li>搜索引擎靠 HTML 结构理解内容优先级,<code><h3></h3> 在 <li> 内会被视为该结果的主标题,权重高于散落在 <div> 里的同级 <code><h3></h3>
<ul></ul> + <li> 天然支持逐项停顿,<div> 块容易整屏滑走
<li>样式重置更可控:<code>ul#result-list { list-style: none; margin: 0; padding: 0; } 一行就能清掉所有浏览器差异,<div> 得手动补 <code>margin、padding、display,还容易漏掉老 IE 的缩进 bug
input 事件监听比 keyup 更可靠
用户粘贴、语音输入、剪贴板拖入,甚至 IME 输入法上屏时,keyup 都不会触发,但 input 事件会。实时过滤必须用它。
- 绑定方式必须是
searchInput.addEventListener('input', handler),不是oninput属性写法(不利于解绑和复用) - 取值只认
e.target.value,别用this.value或event.srcElement.value,后者在某些 polyfill 下不可靠 - 务必加
.trim():空格开头或结尾会导致startsWith()匹配失败,比如搜" react"匹配不到id="react-router" - 避免高频重绘:不要每次
input都调用innerHTML = ''再拼接,超过 10 条结果就该用document.createElement('li')批量创建后append()
关键词高亮必须防 XSS 和错匹配
用户输 <script>alert(1)</script> 或正则符号如 js+,直接 replace() 会崩或执行脚本。安全高亮不是加个 <mark></mark> 就完事。
立即学习“前端免费学习笔记(深入)”;
- 先转义关键词:
keyword.replace(/[.*+?^${}()|[]\]/g, '\$&')(注意方括号里要双反斜杠) - 再构造正则:
new RegExp(`\b(${escaped})\b`, 'gi'),\b保证全词匹配,避免把javascript里的js错亮 - 高亮前必须清洗原文:
const cleanText = document.createElement('div'); cleanText.innerHTML = rawString; const plain = cleanText.textContent;,再对plain执行高亮 - 插入 DOM 时,标题用
textContent,摘要用innerHTML(仅限已清洗+高亮过的字符串),绝不用innerHTML直接塞原始字段
空状态不能只写“未找到”
空状态是用户决策点,不是报错。HTML 层只需提供明确语义结构,CSS 控制样式,JS 控制显隐。
- 空状态容器必须用
<section role="region" aria-label="搜索无结果提示"></section>,不能只是<div> <li>里面至少包含一句说明(如“没有匹配到以‘xyz’开头的按钮”)和一个操作引导(如“试试其他关键词”或“查看全部列表”)</li> <li>别用 <code>display: none隐藏空状态块——它会让辅助技术完全忽略;正确做法是初始渲染时就不插入,或用aria-hidden="true"+hidden属性配合 JS 切换 - 如果过滤逻辑依赖
id或alt,空状态文案要对应说明:“正在按按钮 ID 或图片描述查找”
最易被忽略的是匹配逻辑和空格处理:用户习惯连打空格再删,trim() 必须放在事件回调最开头;还有人以为 startsWith() 能跨属性匹配,其实它只对字符串生效,想同时查 id 和 alt 得手动拼成一个判断链,比如 (el.id || el.alt || '').toLowerCase().startsWith(query.toLowerCase())。



















