直接用 innerHTML.replace() 高亮会破坏 DOM 结构,导致脚本被切进 mark、表单值清空、事件丢失、嵌套 mark 等问题;根本原因是 innerHTML 是字符串而 DOM 是树,正则替换易错位解析失败;安全方案是用 document.createTreeWalker() 遍历文本节点,跳过非文本及敏感节点,匹配后创建 mark 替换并 normalize。

为什么不能直接用 innerHTML.replace() 做高亮
页面大概率会崩——<script></script> 被切进 里、 的值清空、事件监听器全部丢失,甚至出现嵌套 关键词。根本原因是 innerHTML 是字符串,但 DOM 是树;正则一替换,标签结构就错位,浏览器解析失败后白屏或静默降级。
document.createTreeWalker() 安全遍历文本节点
这是零依赖、IE9+ 兼容、不破坏交互的底线方案。核心只碰 Node.TEXT_NODE,跳过所有元素、脚本、样式、表单控件节点。
- 从目标容器开始:比如
const container = document.querySelector('.article-content'),别直接操作document.body - 创建遍历器:
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false) - 循环中过滤无效节点:
if (!node.textContent.trim() || node.parentElement?.tagName === 'SCRIPT' || node.parentElement?.tagName === 'TEXTAREA') continue - 对每个
node.textContent执行匹配,**绝不**用node.parentNode.innerHTML - 匹配后创建
mark元素:const mark = document.createElement('mark'); mark.textContent = matchedText;,再用node.parentNode.replaceChild(mark, node) - 每次替换后调用
node.parentNode.normalize(),合并相邻文本节点,防止重复高亮
中文关键词必须加 Unicode 边界断言
搜“服务”,不希望把“服务器”“微服务”“服务商”全切开。简单 /服务/gi 会语义断裂,但又不能强上分词库。折中解法是用正向断言模拟词边界:
- 先转义关键词:
const escaped = keyword.replace(/[.*+?^${}()|[]]/g, '\$&') - 再构造正则:
new RegExp(`(? (? 检查左侧是否为空格、汉字或行首;(?=...)同理检查右侧- 仍会漏掉“服务费”这类复合词,但比全局匹配可读性高得多,且无额外依赖
mark 标签不是“看起来黄就行”,而是语义与兼容性关键
mark 是唯一被屏幕阅读器识别、被搜索引擎理解为“搜索命中”的语义化标签。但它在旧版 Safari 和 Windows 高对比度模式下默认样式不可靠。
- 必须显式重置样式:
mark { background-color: #ffeb3b; color: inherit; padding: 0 2px; } - 避免子元素颜色被覆盖:加
mark * { color: inherit !important; } - 不要用
span+style="background: yellow"替代,它不传达语义,也不被辅助技术识别 - 用户输入含
<script></script>或引号时,务必先做 HTML 转义,否则textContent提取后拼进 DOM 仍是安全的,但若误走innerHTML路径就直接 XSS
高亮逻辑本身不难,难的是边界处理:中文词界、DOM 结构保护、用户输入转义、样式降级兜底。漏掉任意一环,上线后都会在某个用户那里突然失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











