应优先用 textContent 渲染纯文本,避免 innerHTML 引发 XSS;必须用 HTML 时,需用 DOMPurify 白名单净化;服务端须默认转义,前端净化仅为最后一道防线。
直接用 innerHTML 渲染用户输入 = 默认开启 XSS 通道,必须绕开或严加净化。
用 textContent 替代 innerHTML 的适用场景
只要内容是纯文本(比如用户名、评论正文、搜索关键词、API 返回的 data.title),就该无条件用 textContent。它不解析任何标签,自动把 、>、& 等转义为对应 HTML 实体,连 都只是显示为文字。
常见错误:把 URL 参数 location.search 解析后直接塞进 innerHTML 做高亮;正确做法是先取值,再赋给 textContent:
const params = new URLSearchParams(location.search);
const keyword = params.get('q') || '';
document.getElementById('hint').textContent = `搜索关键词:${keyword}`;
- 不会执行脚本,也不会触发
onerror、onclick等事件属性 - 性能比
innerHTML更高,浏览器不用重新解析 DOM 树 - 对隐藏元素(如
display: none)也返回全部文本,行为稳定
需要保留简单格式时,手动转义比依赖库更可控
当业务要求支持粗体、换行、链接等有限 HTML(比如后台编辑的简短公告),又不想引入 DOMPurify 这类依赖,可只对危险字符做最小化转义:
立即学习“前端免费学习笔记(深入)”;
INFINITE ALBUM
面向游戏玩家的生成式AI音乐
下载
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
el.innerHTML = escapeHtml(userInput);
- 仅在
innerHTML不可避免时使用,不能代替白名单过滤 - 不处理
javascript:协议、on*事件、javascript这类编码绕过,所以仍需配合 CSP - 若内容含链接,应额外校验
href值是否以http://、https://或/开头,拒绝javascript:、data:
必须渲染富文本时,DOMPurify 是当前最可靠的选择
管理员后台发公告、Markdown 渲染结果、邮件模板预览等场景,确实需要执行部分 HTML。此时不能靠正则“删掉 ”,而要用专为浏览器设计的净化库:
import DOMPurify from 'dompurify'; const dirty = 'Hellofetch("/steal")'; const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b', 'i', 'u', 'p', 'br', 'a'], ALLOWED_ATTR: ['href'] }); el.innerHTML = clean;
- 默认已禁用
on*事件、、、javascript:等全部高危项 - 配置
ALLOWED_TAGS和ALLOWED_ATTR比写正则更安全、可维护 - 注意:它只处理字符串,不修复
insertAdjacentHTML、document.write或v-html的误用
服务端不转义,前端再怎么防都是补漏
前端净化只是最后一道防线。如果后端接口返回的是未转义的 HTML 字符串(比如 {"content":"),前端拿到后无论用
"} textContent 还是 DOMPurify,都意味着数据源头已失守。
- JSON 接口字段应默认做上下文编码:HTML 上下文中输出时,
<必须变成 - 服务端模板(如 Jinja、Nunjucks)必须启用默认转义,禁用
{% raw %}类标记 - 响应头加
Content-Security-Policy: default-src 'self'可阻止内联脚本执行,但无法防止img打点或fetch泄露
真正难防的不是 ,而是那些看似无害的属性和协议——比如 src 指向恶意域名、href 跳转到钓鱼页、onload 触发二次请求。这些细节一旦漏检,净化就形同虚设。