必须遍历所有含on开头原生事件属性的元素并用removeAttribute清除,覆盖动态插入和Shadow DOM场景,禁用innerHTML重写与正则替换以防XSS残留。

直接删 onclick 这类属性不是“删完就完事”,而是必须匹配全量事件名、遍历所有元素、避开 innerHTML 重写,并覆盖动态插入场景——否则残留一个 onerror 就可能被触发 XSS。
哪些属性算“注入的事件属性”?
指 HTML 标签里以 on 开头的原生属性,比如:onclick、onsubmit、onload、onfocus、oninput、onkeydown、onerror、onmouseover 等。它们是 HTML 属性,不是 JS 绑定的监听器,浏览器会在解析时立即执行其中代码。
注意:data-on-click、x-on:click(Alpine)、@click(Vue)不属于此列,不用删;但 onabort、ondragstart 这类冷门属性也得纳入清洗范围。
用 querySelectorAll + removeAttribute 逐个清除
这是唯一推荐的 DOM 层面清理方式,不破坏结构、不丢事件监听器、不触发重排。
立即学习“前端免费学习笔记(深入)”;
- 先定义要清的属性列表:
['onclick','onsubmit','onchange','onload','onerror','onfocus','onblur','onkeydown','oninput','onmouseover','onmouseout','onabort','ondragstart'] - 用
querySelectorAll一次性选中所有含任一事件属性的元素:document.querySelectorAll('[onclick], [onsubmit], [onchange], [onload], [onerror], [onfocus], [onblur], [onkeydown], [oninput], [onmouseover], [onmouseout], [onabort], [ondragstart]') - 对每个匹配元素循环调用
el.removeAttribute(attr),注意传入的是完整属性名(如'onclick'),不是去掉on的'click' - 如果页面用了 Shadow DOM,需额外遍历
shadowRoot.querySelectorAll(...)
为什么不能用 innerHTML = innerHTML 或正则替换?
这两种做法看似省事,实则埋雷:
-
element.innerHTML = element.innerHTML会强制重新解析整个子树:已绑定的addEventListener全部丢失;input的当前value值不会保留;焦点会意外丢失;requestAnimationFrame回调可能中断 - 正则如
/on\w+\s*=\s*["'][^"']*["']/gi容易漏掉换行、注释内伪代码、或误杀data-on-id这类自定义属性;更危险的是,它无法识别属性值被编码的情况,比如onclick="alert('1')" - 某些 SSR 框架(如 Vue/Nuxt)注入的
data-v-xxx属性,正则一并删掉会导致组件无法复用或响应式失效
动态插入的内容怎么防?
静态 HTML 清一遍只是起点。真正难控的是后续 JS 动态插入的片段:
- 所有拼接 HTML 字符串的地方(如
el.innerHTML += '<button onclick="...">'</button>),必须在拼接前过滤:用str.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')预处理 - 对
insertAdjacentHTML、document.write、v-html、dangerouslySetInnerHTML等高危 API,前置调用DOMPurify.sanitize(html, { FORBID_ATTR: ['onerror', 'onclick', ...] }) - 后端返回的 HTML 若含事件属性,前端清洗只是兜底——必须推动后端改模板,禁用服务端拼接
onclick,这才是根治
最常被忽略的一点:清理动作本身不会自动作用于未来插入的节点。你得把清洗逻辑封装成函数,在每次插入前显式调用,而不是只在页面加载时跑一次。



















