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) { }

此时:

  1. HTML 解析到<img>,开始请求图片。
  2. 遇到<script src="huge.js">,停止解析 HTML。
  3. huge.js 执行 10 秒。
  4. huge.js 执行完后,HTML 继续解析。
  5. 执行后面的脚本,给图片绑定onload
  6. 图片加载完成时,触发onload

情况2:图片已经加载完成了

这是你代码里的一个潜在问题。

如果img图片很小,或者已经在浏览器缓存中。而juge.js执行时间很长

那么可能发生:

  1. 图片早就下载完成。
  2. 但后面的 JS 还没执行到。
  3. 你还没有给图片绑定: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 的实现思路。