本质是浏览器将已转义的字符串当作纯文本解析而非html:用innerhtml插入已转义内容(如

HTML格式化后字符被转义,本质不是“格式化”导致的,而是你把本该当纯文本处理的内容,错误地塞进了 HTML 解析上下文里。 比如用 innerHTML 插入已转义的字符串、或在框架中用双大括号插值展示本应渲染的 HTML 片段,都会让 变成字面量显示,而不是解析为标签。
为什么 innerHTML 显示的是
因为你给 innerHTML 赋的值本身已经是转义后的字符串(比如服务端返回的 "<div>Hello</div>"),浏览器会把它当作普通文本再解析一次:先解码成 <div>Hello</div>,再插入 DOM —— 但如果你没做这步解码,它就原样显示为 <div>。常见于后端用了 <code>html.escape(),前端又直接 el.innerHTML = data.html。
- 确认数据来源:如果是 JSON API 返回的字段,大概率是原始字符串;如果响应头是
text/html或模板直出,大概率已转义 - 不要手动拼接:
el.innerHTML = '<strong>' + text + '</strong>'是高危且易错的写法 - 可信内容才用
innerHTML;否则优先用textContent,它自动跳过所有解析逻辑
React/Vue 中双大括号为啥不渲染 HTML
因为 {data.html}(React)或 {{ data.html }}(Vue)默认走的是 textContent 语义,等价于安全转义。这不是 bug,是防 XSS 的设计。想让它渲染 HTML,必须显式声明:
- React:用
dangerouslySetInnerHTML={{ __html: data.html }},且确保data.html已清洗或完全可信 - Vue:用
v-html="data.html",同样禁止用于用户输入内容 - 别对同一字段既在后端
html.escape(),又在前端dangerouslySetInnerHTML—— 这会导致双重转义,显示为<div>
怎么安全地反转义再渲染
只在明确需要还原 HTML 含义时才做反转义,且必须前置清洗。Python 用 html.unescape(),JS 推荐用 DOMParser 避免正则遗漏:
function htmlDecode(str) {
const doc = new DOMParser().parseFromString(str, 'text/html');
return doc.documentElement.textContent;
}
但注意:这个函数只还原转义字符,不处理 XSS。如果你拿到的是用户提交的富文本,反转义后仍需用 DOMPurify.sanitize() 等工具清洗,再塞进 innerHTML。
最常被忽略的一点:**转义/反转义不是格式化操作,而是上下文切换动作**。你在 JSON 字段里存 "<p>test</p>",是为了让它作为字符串安全传输;你把它拿出来渲染,才需要决定是当文本显示,还是当 HTML 执行 —— 这个决策点不能模糊,也不能跨层叠加。











