标签只是语义容器,高亮需javascript操作文本节点;innerhtml.replace()危险因引发xss、dom破坏等;安全做法是treewalker遍历文本节点、转义后创建mark插入;样式须显式声明并适配暗色模式;框架中应避免dangerouslysetinnerhtml,保留语义与可访问性。

结果包含 关键词
` 只对静态内容有效,动态搜索必须走文本节点操作。为什么 innerHTML.replace() 是最危险的写法
这是新手踩坑率最高的地方:用字符串替换整个元素的 innerHTML,会同时触发 XSS、DOM 结构破坏和 HTML 解析中断。
- 用户搜
<script>alert(1)</script>→ 拼进字符串后可能直接执行脚本 - 原文是
<img src="logo.png">,搜logo→ 变成<img src="<mark>logo</mark>.png">,图片挂掉 - 关键词含正则元字符(如
.、[a-z])→new RegExp(keyword)报错,脚本中断 - 原文含
&或"→ 替换后 HTML 实体错乱,后续标签解析失败
必须只遍历和替换文本节点(NodeFilter.SHOW_TEXT)
安全高亮的本质是绕过字符串拼接,只在纯文本内容上做切分与替换。核心路径是 TreeWalker → 匹配 textContent → 创建 mark 元素 → 插入 DOM。
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false)获取所有文本节点 - 跳过空或全空白节点:
node.textContent.trim() === '' - 关键词先做 HTML 转义:
keyword.replace(/[&"'`=/]/g, c => `${c.charCodeAt(0)};`) - 再做正则转义:
keyword.replace(/[.*+?^${}()|[]]/g, '\$&') - 匹配后不用字符串拼接,而是:
const mark = document.createElement('mark'); mark.textContent = matchedText; node.parentNode.replaceChild(mark, node);
样式必须显式声明,不能依赖浏览器默认值
浏览器默认的 #ff0 黄色背景在暗色模式、高对比度设置、阅读模式、邮件客户端甚至打印时基本不可见。
- 至少写死这三项:
background-color、color、padding - 深色模式适配必须手动加:
@media (prefers-color-scheme: dark) { mark { background-color: #ffca28; color: #121212; } } - 别用
background: none—— 它不会清空background-image,应写background-color: transparent或initial - 中文搜索不加词边界(
无效),但要注意全角/半角一致性;英文建议用(? 避免误匹配
Vue/React 中别滥用 v-html 或 dangerouslySetInnerHTML
框架里直接插渲染后的 HTML 字符串会丢失响应式、破坏事件绑定、且易引入 XSS。
- Vue 3 Composition API:用
computed(() => splitText(content, keyword))返回结构化数组,模板中v-for渲染不同 type 的节点 - React:用
useMemo拆分字符串,返回ReactNode[],每个<mark></mark>单独包裹,避免用dangerouslySetInnerHTML - 关键点:保留原始文本节点结构,不破坏父容器的 CSS 选择器作用域和事件监听器
mark 明确播报“这是搜索命中”,搜索引擎可能提取它生成摘要,而这些能力在嵌套 mark、用 span 替代、或样式未显式声明时全部失效。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











