onload 不触发主因是资源未成功加载或事件监听顺序错误;应先绑定 addeventlistener 再设置 src,并对 img 主动调用 decode() 确保解码完成。

img 的 onload 为什么有时不触发?
因为 onload 只在资源**成功加载完成**时触发,且前提是 DOM 中该 <img> 元素已存在、src 已设置、浏览器尚未缓存命中并同步完成解码(极少数情况)。
- 如果
src是空字符串、data:URI 解析失败、或图片格式损坏,onload不会触发,onerror会触发 - 如果元素插入 DOM 前就设置了
onload,但src是之后才赋值的,没问题;但如果src赋值前图片已被缓存且浏览器提前加载完毕(如预加载),onload可能已错过 —— 此时需检查complete属性 - 动态创建
<img>时,务必先设onload再设src,顺序反了在某些浏览器(尤其是旧版 Safari)中可能丢失事件
script 的 onload 和 defer/async 的关系
<script onload="..."></script> 仅对**外部脚本**(带 src)有效,且只在脚本下载、解析、执行全部完成后触发。它和 defer 或 async 并非互斥,但行为有关键差异:
-
async:脚本异步下载,完成后立即执行,onload在执行结束时触发;多个async脚本不保证顺序 -
defer:脚本异步下载,但要等到 DOM 解析完成(DOMContentLoaded前)才按顺序执行,onload在其执行完后触发 - 没加
async/defer的外部脚本:阻塞 HTML 解析,onload在其执行完后触发 —— 但此时页面渲染已严重延迟,不推荐
替代方案:用 addEventListener 更可靠
直接写 onload 属性容易被覆盖,也不支持多个监听器。现代写法应优先用 addEventListener:
const img = document.createElement('img');
img.addEventListener('load', () => console.log('loaded'));
img.addEventListener('error', () => console.log('failed'));
img.src = '/path/to/image.jpg'; // 注意:必须放最后
对 <script></script> 同理:
const script = document.createElement('script');
script.addEventListener('load', () => console.log('script executed'));
script.src = '/path/to/app.js';
document.head.appendChild(script);
- 事件监听器不会被重复赋值覆盖
- 可多次调用
addEventListener绑定不同逻辑 - 注意:IE9–IE11 不支持
script.onload,但支持script.onreadystatechange(需判断script.readyState === 'loaded' || script.readyState === 'complete')
资源加载完成 ≠ 页面可用,别忽略 decode
尤其对 <img>,onload 触发只代表图像数据已下载并解析为位图,但**尚未完成解码(decode)** —— 这在大图、WebP/AVIF 等格式上可能明显卡顿。用户滚动到图片位置时仍可能看到空白或重排。
- 真正“可安全绘制”的时机是
img.decode()Promise 完成后(现代浏览器支持) - 例如:
img.addEventListener('load', async () => { try { await img.decode(); // 等待 GPU 解码完成 img.classList.add('ready'); } catch (e) { console.error('decode failed', e); } }); - 不调用
decode()时,首次绘制仍会隐式触发解码,可能造成卡顿;主动调用可将耗时前置,配合 loading 状态更可控
这个点常被忽略:onload 是加载链路的中间节点,不是终点。











