onerror 触发后直接修改 src 会无限重试,因浏览器将每次 src 赋值视为新请求;应通过 dataset 记录重试次数并限制重试逻辑,或用 fetch 预检避免死循环。

onerror 触发后直接修改 src 会无限重试
浏览器对 img 元素的 onerror 事件没有内置重试计数限制,只要你在 onerror 回调里再次赋值 src(哪怕只是设成相同 URL),就会触发新一轮加载 → 失败 → 再次进入 onerror,形成死循环。常见表现是控制台刷屏报 404,CPU 升高,页面卡顿。
根本原因是:**onerror 本身不区分“首次加载失败”和“重试失败”,你每次改 src,它都当成一次新请求来对待。**
- 不要在
onerror回调里无条件设置src - 必须用外部状态(如变量、dataset)记录已尝试次数
- 重试逻辑应限于特定场景(如 CDN 域名切换、带时间戳的缓存绕过),而非盲目换 URL
用 dataset 记录重试次数并主动终止
最轻量、无依赖的做法是利用元素自身的 dataset 存储状态。把重试计数挂载到 img 上,每次进入 onerror 先读取、判断、再决定是否重试。
@@##@@
handleImageError 函数示例:
function handleImageError(img) {
const count = parseInt(img.dataset.retryCount) || 0;
const max = parseInt(img.dataset.maxRetry) || 1;
if (count >= max) {
img.src = '/images/placeholder.png'; // 终止,换兜底图
return;
}
// 简单重试:加时间戳绕缓存
const url = new URL(img.src);
url.searchParams.set('t', Date.now());
img.src = url.toString();
img.dataset.retryCount = count + 1;
}
-
dataset是 DOM 元素原生属性,无需额外状态管理 - 避免用全局变量或闭包计数,否则多个图片会互相干扰
- 重试 URL 改动要有意义(如换域名、加参数),否则重复请求同一地址仍会失败
更健壮的方案:用 Promise + fetch 预检再加载
如果业务允许延迟渲染(比如非首屏图片、懒加载场景),可完全绕开 onerror 循环问题:先用 fetch 检查图片是否存在,成功后再设置 src。这样加载逻辑可控,失败即止,不触发 onerror。
async function loadSafeImage(img, url) {
try {
const res = await fetch(url, { method: 'HEAD' });
if (res.ok && res.headers.get('content-type')?.startsWith('image/')) {
img.src = url;
} else {
throw new Error('Not an image');
}
} catch {
img.src = '/images/placeholder.png';
}
}
// 使用
loadSafeImage(document.getElementById('my-img'), 'https://cdn2.example.com/photo.jpg');
- HEAD 请求体积小、速度快,适合预检
- 注意 CORS 限制:若图片跨域且服务端未配置
Access-Control-Allow-Origin,fetch会拒绝,需服务端配合或改用同源代理 - 此法不适用于需要立即渲染的首屏关键图(有白屏等待)
容易被忽略的关键点
很多人以为只要限制重试次数就万事大吉,但实际还有两个隐藏雷区:
-
img.src赋值后若 URL 语法错误(如含未编码空格、特殊字符),会立刻触发onerror,且不走网络层 —— 此时重试计数无效,直接死循环。务必确保 URL 经encodeURI或URL构造函数校验 - 某些老版本 iOS Safari 对
data:协议图或 base64 图的onerror行为异常,即使兜底图也触发错误。遇到时优先降级为 CSS background-image +::before替代

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











