onerror 触发循环是因为备用图加载失败会再次触发该事件,导致无限递归;可用 data-try-count 或 WeakMap 限制重试次数,或改用 abortable fetch 主动控制加载流程。

onerror 为什么会触发循环?
当 img 的 onerror 里直接再次设置 src(比如换备用图),而备用图又加载失败,就会再次触发 onerror,形成无限递归调用。浏览器可能卡死或报 RangeError: Maximum call stack size exceeded。
用 CSS 计数器 + data 属性做加载尝试计数
CSS 计数器本身不参与 JS 执行,但可配合 data- 属性记录状态,避免在 JS 中维护易出错的闭包变量。核心思路:每次进入 onerror 先读取 data-try-count,大于等于阈值就不再重试。
实操建议:
- 初始化时给
img设置data-try-count="0" - 在
onerror回调中用parseInt(this.dataset.tryCount || '0')读取当前次数 - 只在
count 时才赋新src,并更新dataset.tryCount = count + 1 - 不要用全局变量或外层闭包存计数——多个图片共用同一 handler 时会互相干扰
@@##@@
function handleImageError(img) {
const count = parseInt(img.dataset.tryCount || '0');
if (count >= 2) return;
img.src = `/fallback-${count + 1}.jpg`;
img.dataset.tryCount = count + 1;
}
用 WeakMap 存储每个元素独立计数(推荐)
比 dataset 更健壮:避免属性名冲突、不污染 DOM、支持任意元素类型(如 iframe 或自定义组件)。WeakMap 的 key 是元素引用,GC 友好。
实操建议:
- 声明一个模块级
const tryCountMap = new WeakMap() - 在
onerror中用tryCountMap.get(img) || 0获取计数 - 重试前检查是否超限;成功加载后建议调用
tryCountMap.delete(img)清理(非必须,但利于调试) - 注意:不能用普通
Map,否则会导致 DOM 元素无法被回收
const tryCountMap = new WeakMap();
function handleImageError(img) {
const count = tryCountMap.get(img) || 0;
if (count >= 2) return;
img.src = `/fallback-${count + 1}.jpg`;
tryCountMap.set(img, count + 1);
}
更彻底的解法:用 Promise + abortable fetch 替代 onerror
onerror 是被动监听,控制力弱;现代做法是主动加载资源并捕获失败。尤其适合需要统一错误上报、灰度降级、或加载 SVG/JSON 等非图片资源的场景。
实操建议:
- 用
fetch()请求图片 URL,检查response.ok和response.type === 'image/*' - 失败时再 fallback 到下一张 URL,手动赋值
img.src(此时不会触发新的onerror,因为不是由 src 变更引发) - 加
AbortController防止页面卸载后还在 resolve Promise - 注意:Safari 对
fetch图片的 CORS 处理较严格,若资源无 CORS header,需配mode: 'no-cors'(但会失去 status 判断能力)
真正容易被忽略的是:**同一个 img 元素多次设置 src,即使最终加载成功,中间失败仍会触发 onerror —— 所以计数必须绑定到元素实例,而非 URL 或事件处理器函数本身。**

前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











