多关键词高亮必须按长度降序排序,否则导致嵌套污染、语义断裂、样式错位、读屏器断续及DOM操作异常;需双重转义(HTML+正则)、仅操作纯文本节点、清理旧mark节点。

多关键词高亮必须按长度降序排序
不排序会导致嵌套污染和语义断裂。比如同时高亮 JavaScript 和 Script,若先匹配短词,原始文本 JavaScript is powerful 会被处理成 <mark>Java<mark>Script</mark></mark> is powerful —— 浏览器虽会自动展平为两个 mark,但中间的 Java 和 Script 已被拆开,CSS 样式错位、读屏器播报断续、后续 DOM 操作(如取 textContent)也会漏掉空格或边界字符。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
wordsToHighlight.sort((a, b) => b.length - a.length)保证长词优先 - 中文关键词也需排序,例如
["微服务", "服务"]必须先匹配“微服务”,否则“服务”会从“微服务”中被提前切出 - 若关键词含空格(如
"web api"),需先转义为空白符正则:keyword.replace(/s+/g, '\s+')
遍历文本节点,跳过 script/style/textarea
直接操作 innerHTML 是最常见翻车点:它会把 <script>alert(1)</script> 当普通文本替换,触发 XSS;也会把 <img src="logo.png" alt="HTML多关键词动态高亮怎么做?批量处理步骤" > 中的 logo 错误包裹,导致路径损坏。唯一安全路径是只触碰纯文本节点。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT)获取全部文本节点,比递归childNodes更可靠 - 循环中加守卫:
if (!node.textContent.trim() || node.parentElement?.tagName === 'SCRIPT' || node.parentElement?.tagName === 'STYLE' || node.parentElement?.tagName === 'TEXTAREA') continue - 每次替换后调用
node.parentNode.normalize(),合并相邻文本节点,防止重复高亮或空白错乱
关键词必须双重转义:HTML + 正则
用户输入不可信。搜 <script>alert(1)</script> 或 cat[0],不转义就进 new RegExp() 会直接报错;不 HTML 转义就塞进 DOM,可能执行脚本或破坏结构。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- HTML 转义函数:
keyword.replace(/[&"'`=/]/g, c => `${c.charCodeAt(0)};`) - 正则转义函数:
keyword.replace(/[.*+?^${}()|[]]/g, '\$&') - 顺序不能错:先 HTML 转义,再正则转义。因为
这类实体本身不含正则元字符,而原始 <code> 既是 HTML 特殊字符又是正则非法字符
中文词边界要手动模拟,不能依赖
对中文完全无效——它只认 ASCII 字母、数字和下划线的边界。搜 服务,原文 服务器 会被命中,这不是错配,而是 根本没起作用。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 构造 Unicode 边界正则:
/(? - 其中
(? 是负向先行断言,确保前面不是汉字;<code>(?![\u4e00-\u9fa5])确保后面不是汉字 - 该方案覆盖空格、标点、换行、英文字符等边界,但不解决复合词(如“微服务”里含“服务”),所以仍要靠长度降序排序兜底
真实项目里最容易被忽略的,是每次高亮前没清理旧 mark 节点。用户连续搜索不同关键词,旧 mark 还在 DOM 里,新逻辑又往里面嵌一层,最后变成 <mark><mark>key</mark></mark>,样式层叠、可访问性标签混乱、甚至影响 CSS 选择器作用域。动手前先跑一遍 element.querySelectorAll('mark').forEach(m => m.replaceWith(m.textContent)),比修 bug 省半小时。



















