mark是唯一语义正确、可访问且原生支持的高亮标签;直接innerhtml.replace()必然导致表单清空、事件丢失、xss和dom断裂,安全方案须用treewalker遍历文本节点、双重转义关键词、replacechild插入mark并调用normalize()。

mark 是唯一语义正确、可访问且浏览器原生支持的高亮标签,但直接用 innerHTML.replace() 包裹关键词必然出事——表单值清空、事件丢失、XSS 触发、DOM 结构断裂。
为什么不能直接对 innerHTML 做正则替换
浏览器把 innerHTML 当纯字符串处理,不管里面是 <input> 还是 <script></script>。常见崩点包括:
- 用户搜
.或[,正则直接抛Invalid regular expression - 搜
div,正则跨标签匹配,把<div> 和 <code><code>一起包进<mark></mark>,生成非法 HTML -
<input value="已输入的内容">高亮后变成纯文本,值彻底丢失 - 原文是
<img src="logo.png">,搜logo后变成<img src="<mark>logo</mark>.png">,图片挂掉 - 调用
document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false)创建遍历器 - 循环中加过滤:
if (!node.textContent.trim()) continue,跳过换行/缩进产生的空白节点 - 手动排除禁用区域:
node.parentElement?.tagName === 'SCRIPT' || node.parentElement?.tagName === 'STYLE' || node.parentElement?.tagName === 'TEXTAREA' - 对每个有效节点,用
node.nodeValue做匹配,创建document.createElement('mark'),再用node.parentNode.replaceChild(markEl, node) - 每次替换后立即执行
node.parentNode.normalize(),合并相邻文本节点,防止重复高亮 - 正则元字符转义:
keyword.replace(/[.*+?^${}()|[]]/g, '\$&')(注意]要放在字符组末尾) - HTML 特殊字符转义(防 XSS):
keyword.replace(/[&"'`]/g, c => `${c.charCodeAt(0)};`) - 构造正则时务必加
gi标志:new RegExp(`(${escaped})`, 'gi') - 替换不用字符串模板,改用函数回调:
text.replace(regex, (match) => { const m = document.createElement('mark'); m.textContent = match; return m; }) - 中文搜索需加 Unicode 边界限定:
/(? - Safari 阅读模式下直接剥离 UA 样式,
mark变成无背景纯文本 - Windows 高对比度模式强制重置未声明的
background-color,可能变透明或黑底白字 - 深色主题下默认
#ff0和深灰背景对比度低于 WCAG AA(要求 ≥4.5:1) - 邮件客户端(如 Outlook)根本不认 UA 默认值
- 必须显式声明:
mark { background-color: #ffeb3b; color: #212121; padding: 1px 4px; border-radius: 2px; },并补上暗色模式媒体查询
怎么安全地只操作文本节点
必须绕过字符串拼接,只让正则接触真正的人类可读文本。核心是用 TreeWalker 遍历,跳过所有非文本内容:
关键词含正则元字符或特殊 HTML 字符怎么办
用户输入不可信,必须双重转义:
为什么 mark 样式必须手写
浏览器默认黄色背景在生产环境里根本不可靠:
真正难的不是让字变黄,而是让高亮在 XSS 防御、DOM 结构安全、语义传达、可访问性、暗色/高对比度/打印等所有边界场景下都稳定生效。











