通过JavaScript创建新的img元素并指定onload事件,为什么要在赋值src属性之前指定事件处理程序?

在JavaScript中创建img元素时,应先设置onload事件处理程序再指定src属性,以避免因图片加载过快(尤其是缓存图片)导致事件未被捕获。


浏览器异步加载图片时,若事件监听器未及时绑定,onload事件可能已触发完毕。


推荐做法是:1)创建元素→2)绑定事件→3)设置src。


虽然现代浏览器有优化机制,但"先事件后src"仍是确保可靠触发的稳健方案,必要时可通过检查complete属性处理缓存情况。


通过JavaScript创建新的img元素并指定onload事件,为什么要在赋值src属性之前指定事件处理程序?


竞态条件(Race Condition) 是并发编程中的一个经典问题,指的是:多个线程(或进程、协程等)同时访问共享资源,且最终结果依赖于它们执行的相对时序 。当这种时序不可控时,程序行为变得不可预测、不一致甚至错误


关联阅读推荐

名词解释:竞态条件、原子操作、零拷贝(附:用 Atomics 修复竞态条件的完整 Web Worker 示例)

多线程环境 竞态条件 和 防护策略 详解(附:JavaScript 中进程和线程的总结)


在JavaScript中创建新的img元素时,先指定onload事件处理程序,再赋值src属性 的主要原因是为了避免因图片加载过快而错过事件触发


下面我们来详细解释这个问题以及背后的原理:


核心原因:错过事件的风险

  1. 图片加载的异步性和速度 :当你为img元素的src属性赋值一个有效的图片URL时,浏览器会立即开始异步加载这张图片。这个加载过程非常快,尤其是对于小图片或来自缓存的图片,它可能在极短的时间内(几乎是瞬间)完成。

  2. 事件触发的时机onload事件是在图片成功加载并解码完成后触发的。

  3. 事件监听器的绑定时机 :如果你先设置src,浏览器会马上开始加载。如果在图片加载完成的瞬间,你的onload事件处理程序还没有被绑定到元素上,那么这个已经发生的onload事件就无法被捕获到。你的事件处理程序将永远不会执行。

打个比方

这就像你让快递员(浏览器)去送一个包裹(加载图片),但你告诉快递员收货人地址(src)之后,才打电话通知收货人(事件处理程序)在家等着。

  • 错误顺序:先给地址,再打电话通知。

    • 如果快递员速度极快,在你打电话之前就把包裹送到了,收货人就不在家(没有事件监听),导致无法签收(事件丢失)。
  • 正确顺序:先打电话通知,再给地址。

    • 收货人(事件处理程序)已经在家里准备好了,这时快递员(浏览器)再去送(加载图片),送达时就能顺利签收(触发onload)。

代码示例

推荐做法(先绑定事件,后设src)

javascript 复制代码
// 1. 创建img元素
var img = new Image();

// 2. 先定义onload事件处理程序
img.onload = function() {
    console.log('图片加载成功了,可以安全地操作图片了。');
    // 例如:将图片添加到页面
    document.body.appendChild(img);
};

// 3. 最后设置src属性,开始加载
img.src = 'my-image.jpg';

// 即使图片来自缓存,onload也能被可靠触发

有风险的做法(先设src,后绑定事件)

javascript 复制代码
// 1. 创建img元素
var img = new Image();

// 2. 先设置src属性,浏览器立即开始加载图片
img.src = 'my-image.jpg';

// 3. 后定义onload事件处理程序
// 风险:如果图片加载速度极快(比如来自缓存),此时onload事件可能已经触发过了。
img.onload = function() {
    console.log('这条消息有可能永远不会打印出来。');
};

例外情况与高级处理

虽然"先事件后src"是最稳健的基本规则,但现代浏览器和JavaScript有一些机制可以应对这种情况:

  1. complete 属性检查 :在绑定事件之前,可以检查一下img.complete属性。如果图片已经加载完成(completetrue),则可以直接手动调用处理函数,或者处理已经存在的宽高等信息。

    javascript 复制代码
    var img = new Image();
    img.src = 'my-image.jpg'; // 假设不小心先设置了src
    
    // 检查图片是否已经加载完成
    if (img.complete) {
        // 图片已经在缓存中加载完了,直接处理
        console.log('图片已从缓存加载,直接处理。');
        // 执行相应的逻辑...
    } else {
        // 图片未加载完,再绑定onload事件
        img.onload = function() {
            console.log('图片现在加载完成了。');
        };
    }

    这种方法在你不确定代码执行顺序,或者处理已存在的DOM图片元素时非常有用。

  2. addEventListener 与现代浏览器 :现代浏览器的调度机制使得即使src先设置,紧接着绑定onload,事件通常也不会丢失,因为事件会在当前执行栈清空后才被调度。但依赖这种"通常"行为仍然存在理论上的风险,尤其是在不同浏览器或极端情况下。因此,坚持"先事件后src"仍然是更健壮、更清晰的编码习惯。


总结

在创建img元素并希望处理其onload事件时,总是先设置onload(或使用addEventListener添加load事件的监听器),再设置src属性


这个简单的顺序能确保无论图片是从网络加载还是从缓存读取,你的事件处理程序都能被可靠地调用,从而避免出现难以追踪的bug。

相关推荐
~无忧花开~2 小时前
React元素渲染:核心概念全解析
开发语言·前端·javascript·react.js
qq_283720052 小时前
nestjs实战(六):诺依Nest.js + MySQL 项目改造为兼容达梦8数据库详细教程
javascript·数据库·mysql·达梦·nest.js·诺依
Mr数据杨2 小时前
【通用Vue】学生管理模块通用功能
javascript·vue.js·ecmascript
前端小菜鸟也有人起2 小时前
vue中is的作用和用法
前端·javascript·vue.js
m0_502724952 小时前
vue3在线预览excel表格
javascript·vue.js·excel
酉鬼女又兒2 小时前
零基础入门前端JavaScript 基础语法详解(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·chrome·蓝桥杯
该怎么办呢2 小时前
packages\engine\Source\Core\Cartesian3.js
前端·javascript·cesium
颜酱2 小时前
吃透回溯算法:从框架到实战
javascript·后端·算法
哈哈哈hhhhhh3 小时前
vue----v-model
前端·javascript·vue.js