img与script标签onload函数可能错过的解决办法
img标签的complete属性与decode方法
<body> <img src="a.jpg" id="pic"> <script rc="huge.js"></script> <script> document.getElementById("pic").onload = function() { console.log(document.getElementById("pic").width); } document.title=document.getElementById("pic").width </script> </body>如果huge.js执行很久
// huge.js let start = Date.now(); while (Date.now() - start < 10000) { }此时:
- HTML 解析到
<img>,开始请求图片。 - 遇到
<script src="huge.js">,停止解析 HTML。 - huge.js 执行 10 秒。
- huge.js 执行完后,HTML 继续解析。
- 执行后面的脚本,给图片绑定
onload。 - 图片加载完成时,触发
onload。
情况2:图片已经加载完成了
这是你代码里的一个潜在问题。
如果img图片很小,或者已经在浏览器缓存中。而juge.js执行时间很长
那么可能发生:
- 图片早就下载完成。
- 但后面的 JS 还没执行到。
- 你还没有给图片绑定:pic.onload函数,等 huge.js 结束后才绑定。此时图片的 load 事件已经过去了。结果就是pic.onload函数不会触发
if (img.decode) { await img.decode(); } else { await new Promise(resolve => { if (img.complete) { resolve(); } else { img.onload = resolve; } }); }decode()的特点:
- 图片已经加载 → 立即返回成功
- 图片未加载 → 等待加载完成
- 图片损坏 → Promise reject
不会出现某些浏览器下第一次绘制空白的问题。
script标签是否有complete属性
如果脚本已经加载完成,后面才绑定 onload 呢?
更高级方案3:资源管理器(推荐大型项目)
很多人不知道,Google Maps SDK、Stripe SDK、PayPal SDK 都是这么干的。
class ResourceManager { static scripts = new Map(); static load(url) { if (!this.scripts.has(url)) { this.scripts.set( url, new Promise((resolve, reject) => { const s = document.createElement("script"); s.src = url; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }) ); } return this.scripts.get(url); } }使用:
await ResourceManager.load("huge.js");以后任何地方,都不会重复下载,这其实是很多 SDK Loader 的实现思路。
