标签仅作语义容器,高亮需 javascript 动态处理文本节点;innerhtml.replace() 易致 xss、dom 错乱、标签破坏;应使用 treewalker 遍历文本节点、双重转义关键词、replacechild 安全插入;样式须显式声明并适配暗色模式;中文搜索需模拟词边界且多词按长度降序匹配。

mark 标签本身不实现搜索标记,它只是语义容器;真正起作用的是 JavaScript 动态查找关键词、切分文本节点、安全包裹 mark 的过程。直接写死 <mark>关键词</mark> 无法响应用户输入,也不防 XSS。
为什么 innerHTML.replace() 是最危险的写法
很多代码一上来就用 element.innerHTML = element.innerHTML.replace(/keyword/g, '<mark>$&</mark>'),这会同时触发三类问题:
- XSS 风险:用户搜
<script>alert(1)</script>,拼进字符串后直接执行 - DOM 解析中断:关键词含
&、"、等字符,导致 HTML 结构错乱 - 破坏标签结构:在
<img src="logo.png">中搜logo,结果变成<img src="<mark>logo</mark>.png">,图片挂掉
根本原因是:你操作的是整个 HTML 字符串,而非纯文本内容。
必须只遍历和替换文本节点(NodeFilter.SHOW_TEXT)
安全高亮的核心是绕过字符串拼接,只处理真实文本内容:
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false)获取所有文本节点 - 对每个
node.textContent执行正则匹配,跳过空或全空白节点 - 关键词需双重转义:
escapeHtml(keyword)防 XSS,再用keyword.replace(/[.*+?^${}()|[]\]/g, '\$&')防正则报错 - 匹配后不用字符串拼接,而是:
document.createElement('mark')→mark.textContent = matchedText→parentNode.replaceChild(mark, textNode)
这样能确保 <mark></mark> 只出现在文本流中,不侵入 HTML 标签内部。
样式必须显式声明,不能依赖浏览器默认值
浏览器默认的黄色背景(#ff0)在以下场景会彻底失效:
- 深色模式下对比度不足(
#ffeb3b在暗色背景上才达标) - Windows 高对比度模式强制重置所有未声明的
background-color - Safari 阅读模式剥离 UA 样式,
<mark></mark>变成无背景 - 邮件客户端或打印样式表中完全不可见
务必写死关键样式:mark { background-color: #ffeb3b; color: #212121; padding: 0.1em 0.3em; border-radius: 3px; },并补上暗色适配:@media (prefers-color-scheme: dark) { mark { background-color: #ffca28; color: #121212; } }。
中文搜索要注意字词边界,但别指望正则替代分词
搜“服务”,不希望把“服务器”“微服务”也切开高亮。可用正则模拟词边界:(?,但要注意:
- 该表达式对中英文混排、标点、全角/半角不一致等场景覆盖有限
- 它不是真正分词,无法识别“微信小程序”中的“小程序”是否独立成词
- 若目标文本已含 HTML 实体(如
),需先 normalize 再匹配,否则空格位置错位 - 多关键词时,按长度从长到短排序匹配,防止短词截断长词(如先匹配 “JavaScript”,再匹配 “Script”)
真正容易被忽略的是:动态插入 <mark></mark> 后,若父容器有 contenteditable="true" 或处于 Shadow DOM 内,document.querySelector('mark') 会返回 null——这不是语法错误,而是查询范围或生命周期问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











