直接 innerHTML 拼接 mark 会炸,因为用户输入的搜索词可能含 HTML 字符(如 &、<、>),若未实体转义就插入,会破坏 DOM 结构并触发 XSS 风险。

为什么直接 innerHTML 拼接 mark 会炸?
因为用户输入的搜索词可能含 、<code>&、" 或正则元字符(如 .、*),不处理就塞进 innerHTML,轻则 DOM 解析错乱(比如 <mark>user<script></mark> 被截断),重则触发 XSS(如搜 <img src="https://img.php.cn/" alt="HTML中mark高亮标记 HTML中mark标签在文本搜索中的实战">)。
常见错误写法:el.innerHTML = text.replace(keyword, '<mark>$&</mark>');
- 必须先对
keyword做 HTML 实体转义,再用于正则或字符串拼接 - 正则匹配前还要转义元字符:例如
keyword.replace(/[.*+?^${}()|[]\]/g, '\$&') - 更安全的做法是不用正则替换,改用
textContent+document.createElement('mark')组装节点
怎么让 mark 在深色模式和高对比度下依然可读?
浏览器默认的黄色背景(#ff0)在 Windows 高对比度主题或 Safari 深色模式下常变透明或灰白,文字直接“消失”。不能只靠 UA 样式。
- 必须显式声明
background-color和color,且满足 WCAG AA 级对比度(≥4.5:1) - 推荐组合:
background-color: #ffeb3b; color: #212121;(亮黄底+深灰字)或暗色模式适配:@media (prefers-color-scheme: dark) { mark { background-color: #ffc107; color: #000; } } - 加
padding: 0.1em 0.2em防止文字贴边糊成一团;border-radius: 2px是可选但建议项 - 别用
mark::before或background-image——语义无意义,且部分读屏器不识别
搜索时怎么避免 “cat” 匹配到 “education” 里?
正则没加单词边界()就会跨词匹配,这是中文用户尤其容易忽略的点:英文需锚定边界,中文则要按字切分或用 Unicode 词边界( 对中文无效)。
立即学习“前端免费学习笔记(深入)”;
- 英文场景:用
new RegExp(`\b${escapedKeyword}\b`, 'gi'),确保只匹配独立单词 - 中文场景:
失效,得靠分词逻辑或简单粗暴的“全字符匹配”(如text.includes(keyword)后手动定位),或用/(?(需注意 Unicode 字符支持) - 混合中英文文本时,建议统一用
String.prototype.indexOf()手动遍历位置,再逐段创建mark节点,可控性更高 - 永远别用
replace(/keyword/g, '<mark>$&</mark>')这种写法——它不区分上下文,也不防嵌套断裂
服务端渲染页面里批量高亮,DOM 就绪前能预处理吗?
可以,但必须在 HTML 输出前完成,且不能依赖客户端 JS。关键不是“能不能”,而是“要不要”——如果搜索词来自 URL 参数(如 ?q=React),服务端模板(如 EJS、Nunjucks)就能做安全高亮。
- 服务端需同步完成三件事:URL 解码 → HTML 实体转义 → 正则边界处理 → 插入
<mark></mark>标签 - Node.js 示例(使用
he库转义):text.replace(new RegExp(`\b${he.escape(keyword)}\b`, 'gi'), '<mark>$</mark>') - 注意:服务端生成的
mark仍需前端 CSS 支持,否则样式丢失;若用 SSR 框架(如 Next.js),务必关掉 hydration 时的重复高亮逻辑,否则 DOM diff 会出错 - 真正麻烦的从来不是怎么包那层
<mark>,而是想清楚:这个高亮,到底是给人看的、给读屏器听的,还是给搜索引擎抓的——三者诉求不同,不能一套逻辑全扛



















