应使用 DOMParser + normalize() 替代 innerHTML +=:先解析 HTML 字符串为 Document,过滤白名单标签并移除 style/class/on* 属性,再调用 normalize() 合并文本节点、清除空节点,最后用 createContextualFragment 插入。

为什么直接 innerHTML += 会放大富文本污染
富文本编辑器输出的 HTML 本身已含大量非语义标签、冗余 style、零宽字符和残留属性,直接用 innerHTML += 拼接,等于把脏数据反复塞进 DOM 树——每次拼接都新增一层嵌套、多出若干空文本节点、叠加更多内联样式。浏览器不会自动合并或修复,结果是:childNodes.length 越来越大,textContent 提取失真,CSS 选择器匹配失效,移动端图片溢出,SSR 渲染不一致。
用 DOMParser + normalize() 替代字符串拼接
不要在 JS 里做 htmlString += editor.getHTML() 这类操作。正确路径是:解析 → 过滤 → 归一化 → 插入。
- 用
new DOMParser().parseFromString(html, 'text/html')创建无执行上下文的文档,避免脚本触发 - 遍历
doc.body.children,只保留白名单标签(p、h1–h6、ul/ol/li、strong、em、a、img) - 对每个节点调用
removeAttribute('style')、removeAttribute('class'),删掉所有on*属性 - 对处理后的
doc.body或其克隆节点调用normalize(),合并相邻文本节点、移除空节点 - 用
Range.createContextualFragment()或DocumentFragment插入,不触发重排
DOMPurify 不是万能,但必须用在服务端入库前
前端调 DOMPurify.sanitize() 只防手滑,拦不住绕过 JS 的 POST 请求。真正起作用的是服务端双重净化:
- 入库前:用
Cheerio或jsdom解析 HTML 字符串,检查是否含非法根节点(如出现在内容中)、孤立<img alt="解决动态拼接渲染中富文本导致的HTML代码质量污染问题" >、<meta>错位等 - 渲染前:再跑一次
DOMPurify.sanitize(html, { FORBID_TAGS: ['script', 'iframe', 'object'], ALLOWED_URI_REGEXP: /^https?:\/\// }) - 别信
htmlspecialchars()—— 它会让<strong>加粗</strong>直接显示为源码,不是渲染
小程序 rich-text 和 mp-html 的预处理差异
微信原生 rich-text 只接受结构化 nodes 数组,不能传 HTML 字符串;而 mp-html 虽支持字符串输入,但内部仍会走 DOMParser 解析。二者都要求你在下发前完成清洗:
立即学习“前端免费学习笔记(深入)”;
- 对
rich-text:服务端应将 HTML 转为标准节点数组(如{ name: 'p', children: [{ type: 'text', text: '段落' }] }),并提前过滤掉script、style、iframe - 对
mp-html:虽兼容字符串,但长文本下性能差 3–5 倍,且它默认不过滤javascript:协议,需手动配config.urlParse拦截 - 两者都不支持动态事件绑定,点击图片预览等交互必须在预处理阶段注入
data-*属性,再由外层容器捕获
最易被忽略的一点:normalize() 对字符串无效,只作用于 DOM 节点;而 DOMParser 解析后得到的是 Document 对象,不是字符串——这两步顺序错了,整个清洗就白做了。



















