必须阻断「失败→改src→再失败」链条,最稳妥做法是加载失败后立即卸载事件监听或锁定重试开关,如this.onerror = null或用dataset标记已失败状态。

onerror 触发后反复加载失败资源导致死循环
直接给 onerror 回调里写 src 重赋值,又没做加载状态控制,浏览器就会在新 src 加载失败时再次触发 onerror,形成无限递归。这不是 JS 异常堆栈溢出那种报错,而是 DOM 层面的静默重试循环,页面卡顿、请求暴增、CPU 拉满都可能发生。
核心破局点:必须阻断「失败 → 改 src → 再失败 → 再改 src」这个链条。最稳妥的做法是加载失败后立即卸载事件监听或锁定重试开关。
- 用
this.onerror = null在回调内立刻清除自身事件处理器(兼容所有浏览器) - 或设一个布尔标记(如
data-loaded="failed"),在onerror里先检查,已失败则直接 return - 避免在回调中直接修改
this.src后不加防护——这是绝大多数死循环的源头
img 标签 onerror 中 this 指向与 this.src 修改风险
onerror 作为内联事件处理器执行时,this 指向当前 <img> 元素,看起来可以放心改 this.src。但问题在于:只要新地址仍 404 或跨域被拒,浏览器就继续触发 onerror,而你刚清掉的 handler 可能因 HTML 重解析或框架重新挂载又回来了。
所以不能只依赖 this.onerror = null,还得配合资源兜底策略:
- 备用图地址必须确保可访问(建议走同源静态资源或 CDN 稳定路径)
- 改
this.src前加判断:if (!this.dataset.loaded) { this.dataset.loaded = 'true'; this.src = '//xxx.png'; } - 慎用相对路径——
src="./fallback.jpg"在 SPA 路由下容易因 base URL 变化二次 404
Vue/React 等框架中 onerror 死循环更隐蔽
框架组件内写 @error="handleError" 或 onError={handleError},看似没直接操作 src,但如果 handleError 触发了 state 更新(比如 setSrc(fallback)),而该更新又导致组件重新渲染并创建新的 <img> 标签,旧标签虽销毁,但新标签若仍加载失败,就会再次进 handleError —— 这本质是渲染层循环,不是事件绑定残留。
解决关键:把兜底逻辑收束到单次响应内:
- React 中用
useRef记录是否已降级,handleError里先if (fallbackedRef.current) return - Vue 中用
v-on:error.once(仅 Vue 2.6+ / Vue 3)或在setup里用onErrorCaptured拦截,但注意它不拦截资源加载错误 - 更稳的方式是预加载 fallback 图并缓存到内存(
new Image().src = fallbackUrl),确保切换时 100% 成功
如何验证 onerror 循环是否真正打断
光看页面不卡不代表循环被破除——可能只是 fallback 图本身又失败了,还在默默重试。得从网络面板和元素状态双向确认:
- 打开 DevTools 的 Network 标签,过滤
img,观察失败请求是否只出现 1 次(初始图)→ 1 次(fallback 图),之后不再新增 - 选中 img 元素,在 Elements 面板里看
onerror属性是否为空,或运行getEventListeners($0).error看返回数组长度是否为 0 - 手动删掉 fallback 图文件,刷新页面,如果控制台出现
Failed to load resource仅两次且无后续请求,说明循环已断
真正麻烦的是那些 fallback 图也带 query 参数(如时间戳)、或服务端做了重定向但未返回 200 的情况——它们会让“看似成功”的兜底实际失效,循环照旧。这类细节,调试时最容易漏掉。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











