如何用代理模式实现图片的预加载与错误自动重构

梦强小哥_1733

梦强小哥_1733

2026-04-25

499人浏览

原创

直接new Image()无法可靠处理加载失败,因onerror触发后src重赋值不再生效,错误状态被锁定;需用Proxy隔离状态、每次新建实例并支持可控重试与fallback。

如何用代理模式实现图片的预加载与错误自动重构

为什么直接 new Image() 无法可靠处理图片加载失败?

浏览器对 img 元素的 onerror 触发有严格限制:一旦触发,后续即使 src 改为有效地址,也不会再次尝试加载(除非手动重置 src 为空再赋值)。原生 Image 对象同样受此机制影响,且错误回调里无法直接“重试”——因为 src 已设过,状态已卡在 error。这不是 bug,是规范行为。

代理模式在这里的作用,不是封装逻辑,而是**隔离加载状态与 DOM 渲染**,让每次加载都像一次全新请求,同时把重试、降级、缓存等策略收口到代理实例中。

用 Proxy 封装 Image 实例并支持自动重试

核心是拦截 src 赋值,内部创建新 Image 实例发起请求,并暴露 load / error 链式响应。关键点在于:每次 src 变更都抛弃旧实例,不复用。

  • 不监听原生 onerror 后直接改 src——这会失效;而是在 error 回调里主动构造新 Image 实例重试
  • 重试次数需显式控制,避免无限循环;建议默认 2 次,第 1 次失败后等 200ms 再试
  • 错误时可 fallback 到备用 URL(如 CDN 备源、本地占位图),但 fallback 地址必须和原地址有明确区分,否则会陷入死循环
  • 避免在 proxy 的 set 中直接执行异步操作;应返回 Promise 并由使用者 await 或链式处理
function createImageProxy() {
  let currentImg = null;
  let currentSrc = '';
  
  return new Proxy(
    {},
    {
      set(target, prop, value) {
        if (prop === 'src') {
          currentSrc = value;
          currentImg?.removeEventListener('load', handleLoad);
          currentImg?.removeEventListener('error', handleError);
          
          currentImg = new Image();
          currentImg.addEventListener('load', handleLoad);
          currentImg.addEventListener('error', handleError);
          currentImg.src = value;
        }
        return true;
      },
      get(target, prop) {
        if (prop === 'src') return currentSrc;
        if (prop === 'then') return (onFulfill, onReject) => {
          // 暴露 Promise 接口便于链式使用
          const p = new Promise((resolve, reject) => {
            const handleLoad = () => resolve(currentImg);
            const handleError = () => reject(new Error(`Failed to load ${currentSrc}`));
            currentImg?.addEventListener('load', handleLoad);
            currentImg?.addEventListener('error', handleError);
          });
          return p.then(onFulfill, onReject);
        };
        return undefined;
      }
    }
  );
}

预加载阶段如何避免重复请求与内存泄漏?

预加载 ≠ 立即插入 DOM。很多场景下你只是想提前触发 HTTP 请求,等真正需要时再把已加载的 Image 实例塞进 标签。这时候必须管理好实例生命周期。

  • 每个预加载任务应绑定唯一 key(如 URL + 尺寸参数),用 Map 缓存 Image 实例,避免同一资源多次 fetch
  • 缓存的实例一旦被消费(比如 append 到页面),就从 Map 中删除,否则 Image 对象长期持有会阻碍 GC
  • 如果预加载后长时间未使用(比如 >30s),可主动调用 currentImg?.src = '' 清空,释放连接(尤其对 WebP/AVIF 等大图)
  • 不要在预加载时设置 crossOrigin 除非必要——它会禁用部分浏览器缓存,且跨域失败时 error 不会携带 status code

错误重构时最容易忽略的三个细节

所谓“重构”,是指加载失败后动态替换为其他内容(如 base64 占位图、文字提示、甚至 SVG fallback),而不是简单换 src。这里埋着几个静默失败点:

  • img.onerror 在某些 iOS 版本中不会触发 404,只会触发 0 状态码的 error;需结合 img.naturalWidth === 0 做二次判断
  • 用 canvas.toDataURL() 生成 fallback 图片时,若原图跨域且未声明 crossOrigin="anonymous",会触发 canvas 污染异常,且该异常无法 try/catch,只能靠 canvas.width 是否为 0 来间接识别
  • 服务端返回 200 但 body 是 HTML(如 Nginx 默认 404 页面),此时 img 仍会触发 load 事件——要检查 img.naturalHeight 是否为 0 或宽高比是否异常,才能识别“假成功”

代理本身不解决所有问题,但它把状态判断、重试时机、fallback 分发这些散落在各处的逻辑,收束到一个可测试、可替换的对象里。真正难的不是写代理,而是定义清楚:什么算失败?重试间隔怎么定?fallback 的优先级链怎么组织?这些决策点,比语法更重要。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

5786

5

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

2023.07.04

1389

3

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

2023.07.04

5704

6

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

2023.09.01

1040

4

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

2016

5

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

2023.09.04

3965

4

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

2023.09.12

5213

6

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

2023.09.20

3420

5

java访问控制修饰符介绍
java访问控制修饰符介绍

java访问控制修饰符有四种,分别是public、protected、private、默认访问修饰符。详细介绍:1、public,public是最宽松的访问控制修饰符,被修饰的类、方法和变量可以被任何其他类访问,当一个类、方法或变量被声明为public时,它们可以在任何地方被访问,无论是同一个包中的类还是不同包中的类;2、protected修饰符等等。

2023.09.20

1108

7

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.3万人学习