不能直接用 innerHTML.replace() 高亮,因正则无法识别 HTML 结构,会误匹配标签内容、触发语法错误、破坏 DOM 树;应使用 TreeWalker 遍历文本节点安全处理。

直接用 innerHTML.replace() 做高亮,八成会把 HTML 标签内容一起匹配(比如搜 a,会误匹配 里的 a),导致结构损坏、脚本失效或样式错乱。必须避开标签内部,只处理纯文本。
为什么不能直接对 innerHTML 做正则替换
浏览器解析 HTML 时,innerHTML 是字符串,但其中混着标签和文本。正则不识别 DOM 结构,searchTerm 若含特殊字符(如 .、?、[)会触发 RegExp 语法错误;更危险的是,它会跨标签匹配 —— 比如搜索 div,可能把 、甚至
div 文本内容全包进 ,破坏 DOM 树。
- 匹配
中的abc
abc,结果变成✅abc
- 匹配
中的abc
,结果变成abc❌(非法嵌套) - 搜索
o时,变成ody>,页面直接白屏
用 TreeWalker 遍历文本节点才安全
这是原生、无依赖、兼容性好(IE9+)的方案:只拿到真正的文本节点(Node.TEXT_NODE),跳过所有元素、注释、脚本节点。匹配前先用 node.textContent 判断是否包含关键词,再用 document.createTextNode() 和 element.insertBefore() 安全拆分并包裹。
- 调用
document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false) - 循环中过滤掉空文本节点:
if (!node.textContent.trim()) continue - 构造正则时需转义关键词:
new RegExp(keywords.replace(/[.*+?^${}()|[]\]/g, '\$&'), 'gi') - 每次替换后调用
node.parentNode.normalize(),合并相邻文本节点,防止重复高亮
怎么让第一个匹配项自动滚动到视口中心
高亮完不能只改样式,用户需要立刻看到结果。别用 window.scrollTo() 粗暴滚到顶部,要精准定位到首个 的位置。
- 执行高亮后,查
document.querySelector('.highlight'),如果存在,调用element.scrollIntoView({ behavior: 'smooth', block: 'center' }) - 注意:若目标在折叠的
或display: none区域内,scrollIntoView会静默失败 —— 需提前检查offsetParent === null,并手动展开父容器 - 避免高频触发:如果用户连打 “react”,不要每按一个键都 scroll,只在最终高亮完成后执行一次
mark.js 真香但要注意 scope 和 cleanup
引入 mark.js 能省掉 80% 的边界逻辑,但它默认作用于整个 body,且多次调用 instance.mark() 不会自动清除旧高亮 —— 会越积越多。
- 初始化时限定范围:
const instance = new Mark(document.querySelector('#main-content')),而非document.body - 每次搜索前必须调用
instance.unmark(),否则新旧高亮叠加,DOM 节点爆炸 - 若搜索词含正则元字符(如
[error]),需传{ diacritics: false, separateWordSearch: false }并手动 escape - 它支持跨标签匹配(如“hello world”横跨两个
),但代价是性能略低,长页慎用
最易被忽略的一点:高亮只是视觉反馈,不是语义增强。如果页面要支持无障碍访问(比如屏幕阅读器),得额外给 加 aria-label 或用 role="mark",否则搜索结果对视障用户完全不可见。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











