不能对 innerHTML 做正则替换,因为浏览器不自动转义 HTML 字符,关键词含 alert(1)、& 等会破坏标签结构,导致脚本执行、XSS 风险或 DOM 解析错误。
直接用 innerHTML 替换关键词高亮,90% 的情况下会出问题:表单值清空、事件监听器丢失、img 的 onerror 失效、甚至破坏嵌套结构。必须只操作文本节点。
innerHTML 做正则替换浏览器不会帮你转义 HTML 字符。如果用户搜 <script>alert(1)</script>,或者关键词里含 &、、<code>",innerHTML 会直接执行或解析成标签,轻则样式错乱,重则 XSS 风险。更隐蔽的问题是:一个带 onclick 的按钮,高亮后可能变成纯文本,点击失效;input 框里刚输的字也会被清空。
常见错误写法:
el.innerHTML = el.innerHTML.replace(/keyword/gi, '<mark>$&</mark>');
这种写法跳过了 DOM 树遍历,把整个 HTML 字符串当文本处理,完全无视了节点语义和状态。
立即学习“前端免费学习笔记(深入)”;
核心逻辑是递归访问 DOM 节点,遇到 Node.TEXT_NODE 才做匹配和包裹,其余节点(元素、注释等)跳过。匹配时用 document.createElement('mark') 创建新节点,再用 replaceChild 替换原文本节点。
node.nodeType === Node.TEXT_NODE 判断是否为文本节点/(u4e00-u9fa5|^)s*keywords*(u4e00-u9fa5|$)/gi
new RegExp(keyword, 'gi'),但注意 keyword 必须先 escapeRegExp() 防特殊字符getSelection().getRangeAt(0).cloneRange()),替换完再恢复,否则 getSelection() 返回空highlightText 函数的关键参数与容错点这个函数通常接收三个参数:node(要搜索的根节点)、keyword(用户输入)、caseSensitive(是否区分大小写)。但实际使用中容易忽略几个关键点:
keyword.trim() === '' 时必须提前 return,否则正则变成 //g,会匹配所有空字符串,页面炸开document.body,应限定为具体容器,比如 document.getElementById('content')
split(' '),要用 keyword.split(/s+/).filter(Boolean),再逐个词调用高亮逻辑setTimeout + clearTimeout 控制触发频率,避免每敲一个字就重跑整棵树<mark> 是语义化标签,比 <span class="highlight"> 更合适,且默认有浅黄背景。但默认样式容易被父级 CSS 覆盖,必须强声明:
mark { background: #ffeb3b; color: #212121; padding: 0 2px; border-radius: 2px; }
另外注意:mark 是内联元素,如果它包裹的文本原本在 <p> 或 <div> 里,不影响块级流;但如果父容器设置了 user-select: none,高亮文字将无法被复制——这点常被忽略。
最麻烦的不是怎么标红,而是怎么在标红之后,还让页面保持可交互、可编辑、可选中。所有绕过文本节点的操作,本质上都是在透支 DOM 的稳定性。