onerror 无限循环源于错误处理中再次触发错误,闭包通过私有状态标志位(如 isHandlingError)阻止递归调用;WeakMap 可隔离多元素状态,script 的 onerror 需单独绑定。

onerror 为什么会无限循环
当 onerror 回调里触发了新的错误(比如加载失败后又尝试加载一个不存在的资源、或执行了抛错代码),浏览器会再次触发 onerror,形成递归调用。常见于图片加载失败后自动重试、错误上报时 JSON 序列化失败、或错误处理逻辑本身有 bug。
用闭包封存“是否正在处理中”状态
核心是避免在错误处理过程中再次进入同一处理逻辑。闭包能保存一个私有布尔变量,控制是否放行——不是靠移除事件监听器(那会丢失后续真实错误),而是让 handler 知道“我还在忙,这次先别进”。
示例场景:图片加载失败后延时重试一次:
const img = document.getElementById('my-img');
let isHandlingError = false;
img.onerror = function (event) {
if (isHandlingError) return; // 闭包捕获的标志位,直接退出
isHandlingError = true;
setTimeout(() => {
try {
img.src = img.src + '?retry=' + Date.now(); // 触发新加载
} catch (e) {
// 这里如果出错,不会再次进这个 onerror —— 因为 isHandlingError 仍为 true
console.error('重试时发生不可恢复错误', e);
isHandlingError = false; // 恢复状态,允许下次真正错误进来
}
}, 100);
};
关键点:
-
isHandlingError必须定义在 handler 外层作用域,否则每次触发都会新建一个独立副本 - 必须在
try外设置isHandlingError = true,否则异常会跳过它 - 重试失败后要手动设回
false,否则后续所有错误都被静默吞掉
更安全的做法:用 WeakMap 隔离不同元素的状态
如果页面有多个图片或 script 标签共用同一套错误处理逻辑,用全局变量会互相干扰。改用 WeakMap 按 DOM 节点隔离状态:
const handlingState = new WeakMap();
function makeErrorHandler(srcGenerator) {
return function (event) {
const target = event.target;
if (handlingState.get(target)) return;
handlingState.set(target, true);
setTimeout(() => {
try {
target.src = srcGenerator();
} finally {
handlingState.delete(target); // 无论成功失败都清理
}
}, 200);
};
}
img.onerror = makeErrorHandler(() => img.src + '?t=' + Date.now());
这样每个 自己的状态互不干扰,且 DOM 被回收后 WeakMap 自动清理,无内存泄漏风险。
注意 script 标签的 onerror 不走 window.onerror
动态插入的 <script></script> 加载失败时,其 onerror 是独立触发的,和 window.onerror 无关。如果你在 window.onerror 里做了类似闭包控制,对 script 错误无效。
正确做法是给 script 元素单独绑定并管理状态:
const script = document.createElement('script');
script.src = '/bad.js';
script.onerror = function () {
if (script.__handling) return;
script.__handling = true;
// 重试逻辑...
};
闭包在这里不是万能钥匙——它只解决“同一 handler 内重入”,但跨元素、跨上下文(如 script vs img)、或异步链中未被覆盖的错误分支,仍可能漏掉或误拦。实际项目中建议配合 error.stack 判断错误来源,而不是无差别拦截。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











