不能用 innerHTML.replace() 做高亮,因其将 HTML 当字符串处理,破坏标签结构、清空表单值、丢失事件监听器并引发 XSS;应只操作文本节点,用 TreeWalker 遍历、转义关键词、正则加 gi 标志、替换后 normalize,并谨慎使用 mark.js。

不能用 innerHTML.replace() 做高亮,这是最常见也最危险的起点。 它会把 HTML 当纯字符串处理,一搜 就报错,一搜 p 就把 切成碎片,表单值清空、事件监听器消失、XSS 漏洞敞开——不是“可能出问题”,是必然崩。
为什么直接正则替换 innerHTML 必然失败
浏览器解析 innerHTML 时依赖完整标签结构。正则在字符串层面操作,完全无视 DOM 层级:
- 匹配
a会误包中的a - 关键词含
.、[、?等元字符时,new RegExp(keyword, 'g')直接抛SyntaxError: Invalid regular expression -
高亮后变纯文本,值永久丢失 - 中文无空格分隔(如“人工智能发展”)时,
边界失效,匹配到“人工”“智能”“人工智”等子串
安全做法:只操作文本节点(Node.TEXT_NODE)
必须跳过所有元素、脚本、样式、textarea 节点,只对人类可读的纯文本做处理:
- 用
document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false)创建遍历器,它天然过滤非文本节点 - 循环中加守卫:
if (!node.textContent.trim() || node.parentElement?.tagName === 'SCRIPT' || node.parentElement?.tagName === 'TEXTAREA') continue - 对每个有效节点,用
node.nodeValue进行匹配(不是textContent,后者不可写) - 关键词必须先转义:
keyword.replace(/[.*+?^${}()|[]\]/g, '\$&') - 构造正则时固定加
gi标志:new RegExp(escapedKeyword, 'gi')
高亮后必须 normalize,否则下次搜索会重复包裹
每次用 node.parentNode.replaceChild(markEl, node) 替换文本节点后,立即调用 node.parentNode.normalize():
- 防止相邻文本节点残留,导致同一词被多次
嵌套 - 避免
React Native这类断裂高亮 - 不调用
normalize(),第二次高亮 “React Native” 会变成ReactNative
别信“简单函数封装”,mark.js 也要配对使用
即使引入 mark.js,默认行为在生产环境里照样踩坑:
- 别写
new Mark(document.body),必须限定容器:new Mark(document.querySelector('.content')) - 显式传参:
instance.mark(keyword, { element: 'mark', className: 'highlight', caseSensitive: false }) - 用户输入的关键词必须 HTML 转义:
keyword.replace(/[&"'`=/]/g, c => `${c.charCodeAt(0)};`),否则<script></script>会被执行 - 中文场景下,
mark.js默认用,需手动改用 Unicode 边界:separateWordSearch: false+ 自定义acrossElements: true
真正难的不是写出正则,而是守住“只碰文本节点”这条线——DOM 结构、事件绑定、表单状态、光标位置,全系于这一条判断上。漏掉一个 SCRIPT 或一次 normalize,就是线上事故的伏笔。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











