mark标签仅为语义容器,需手动提取关键词并包裹;innerHTML.replace()易致XSS、HTML错乱及标签属性污染;应遍历文本节点、转义正则、降序匹配中文词、清理旧mark、手写样式。

mark 标签本身不触发高亮,它只是语义容器;真正起作用的是你手动把关键词从文本里找出来、切开、包上 mark、再塞回 DOM 的过程。直接写 关键词 只对静态内容有效,用户一搜就失效。
为什么 innerHTML.replace() 一用就崩
这是最常见也最容易翻车的写法,表面简洁,实则三重风险并存:
- 用户搜
<script>alert(1)</script>,拼进字符串后直接执行 XSS - 关键词含
&、"、等字符,导致 HTML 结构错乱(比如变成price & cost
)price & cost
- 原文是
,搜
logo后变成,图片挂掉
根本问题在于:你在操作整个 HTML 字符串,而不是纯文本内容。
安全做法:只遍历文本节点,不用字符串拼接
核心是绕过 innerHTML,只处理真实文本节点(nodeType === 3),用原生 DOM 方法插入 mark:
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT)获取所有文本节点 - 跳过空节点和全空白节点:
!textNode.textContent.trim() - 关键词先转义正则元字符:
keyword.replace(/[.*+?^${}()|[]]/g, '\$&') - 构造正则时加
gi标志,用捕获组保留原始大小写:new RegExp(`(${escaped})`, 'gi') - 匹配后创建新节点:
const mark = document.createElement('mark'); mark.textContent = matchedText;,再调用textNode.replaceWith(mark)
mark 样式必须手写,不能靠浏览器默认
浏览器默认的黄色背景在以下场景会彻底消失:
- Safari 阅读模式剥离 UA 样式,
mark变成无背景纯文本 - Windows 高对比度模式强制重置未声明的
background-color - 深色主题下默认
#ff0和深灰背景对比度低于 WCAG AA 要求 - 邮件客户端(如 Outlook)根本不认 UA 默认值
至少写死这三条:background-color、color、padding。推荐写法:
mark {
background-color: #ffeb3b;
color: #212121;
padding: 0.1em 0.3em;
border-radius: 3px;
}
@media (prefers-color-scheme: dark) {
mark { background-color: #ffc107; color: #000; }
}
中文多词匹配要按长度降序排序
中文没有天然词边界,如果关键词数组是 ['Script', 'JavaScript'],不排序就会先匹配 Script,把 JavaScript 拆成 JavaScript。
- 先对关键词数组按字符串长度降序排序:
words.sort((a, b) => b.length - a.length) - 再拼进正则:
new RegExp(`(${escapedWords.join('|')})`, 'gi') - 避免嵌套高亮:替换前检查父节点是否已有
mark,或每次高亮前先清理旧标记:element.querySelectorAll('mark').forEach(el => el.replaceWith(el.textContent))
真正难的不是写对一行正则,而是守住「只动文本、不动标签」这条线——一旦越界,DOM 就不可逆地错乱了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










