关于vue中image控件,onload事件里,event.target 为null的奇怪问题探讨

废话不多说(主要文笔比较差),直接上代码

一个简单的demo,如下

复制代码
<img :src="orginalImgSrc" style="display: none;" crossOrigin="Anonymous" @load="imgLoaded">

vue代码

复制代码
  imgLoaded(e) {
                debugger
                console.log('event',e);
                console.log('target',e.target);               
                return;            

            }

这时候,会发现

console.log里的event对象,target始终为null,但是如果debugger进去看,又是可以看到taget对象的

下图为debugger模式下:

下面为控制台

可以清晰的看到,控制台的输出里,target对象始终为null,由于我一开始只输出了event对象,看到target对象为null后百思不得其解,认定从这里取target对象,必然也是null

实际上此处是可以直接获取到target对象的.

那么为什么呢?

参考相关资料后得知

这个问题是事件循环机制导致的,在JavaScript中,事件处理程序是在事件循环任务队列中异步执行的.

但@load事件触发的时候,event事件对象会被正确传递给事件处理程序,demo中为imgLoaded(e),在控制台输出之前,JavaScript引擎已经执行了后续的代码.

但是在这个过程中,某些浏览器优化或者垃圾回收机制导致e.target引用被移除或者清除了,导致第一次打印e.target的时候,显示为null

第二次打印的时候.e.target是一条新的一句,JavaScript的引擎会重新计算e.target的值,所以又获取到了正确的元素引用.

因此通常建议在程序开头,将e.target存储到一个局部变量中.然后再后续的代码中使用该变量,防止JavaScript的引擎执行机制丢失对目标元素的引用

再举个例子,会被优化掉target的例子

复制代码
   const tempImg = new Image();

tempImg.setAttribute('crossOrigin', 'Anonymous');
tempImg.src = this.imgResize;

console.log('*****************');
tempImg.onload = (ev) => {
console.log('onload', ev);
console.log('ev.target',ev.target);
setTimeout(() => {
console.log('settimeout---target:',ev.target) // <- HTMLElement
}, 0)
}

输出结果如图

是不是有点奇怪,上面明明说.e.target应该会重新计算的,为什么在settimeout里,并没有重新计算呢?

当 onload 事件触发时,ev 参数就是一个指向事件对象的引用。但是,一旦事件处理程序执行完毕,这个引用就会被清除,以节省内存。

在你的代码中,setTimeout 函数会在当前执行上下文结束后,将其回调函数加入事件循环队列。当事件循环再次运行时,它会从事件队列中取出回调函数并执行。

问题在于,当 setTimeout 的回调函数执行时,原始的事件对象 ev 已经不存在了,因此 ev.target 自然就变成了 null。

这个问题在基于 Blink 内核的浏览器中尤其常见,如 Chrome、Edge 等。它们会在事件处理程序执行完毕后,主动清除对事件对象的引用,以优化内存使用。

但是,如果我在setTimeOut里输出ev也就是event对象呢?event对象依然有,只不过target还是null,为什么呢?

这是因为事件对象的生命周期与目标元素的引用生命周期不同。

在 Web 浏览器中,事件对象 ev 本身是一个对象,它包含了许多属性,如 type、currentTarget 等,用于描述事件的细节。但其中的 target 属性指向触发该事件的 DOM 元素。

当事件处理程序执行完毕后,浏览器会清除对目标元素 (ev.target) 的引用,以释放内存。但事件对象 ev 本身并不会被立即销毁,它可能还会在其他地方被使用或引用。

所以在代码中,当 setTimeout 回调函数执行时,虽然 ev.target 已经变成了 null,但 ev 对象本身仍然存在,只是它的 target 属性已经被清除了。这就解释了为什么 console.log('settimeout---ev:', ev) 能够打印出事件对象,但 ev.target 却是 null。

这种行为是浏览器的优化机制,它只清除了对目标元素的引用,而保留了事件对象本身,以防止意外地破坏了其他依赖于该事件对象的代码。

总的来说,在异步操作中访问事件对象的属性时,最安全的做法是将需要使用的属性值提前存储到其他变量中,而不是直接引用事件对象的属性,因为这些属性的生命周期可能会比事件对象本身更短。

所以如果用局部变量接收,就可以正确输出,代码如下

复制代码
 const tempImg = new Image();
                tempImg.setAttribute('crossOrigin', 'Anonymous');
                tempImg.src = this.imgResize;

                console.log('*****************');
                tempImg.onload = (ev) => {
                    console.log('onload', ev);                 
                    console.log('ev.target',ev.target);
                    const t_target=ev.target;
                    setTimeout(() => {
                        console.log('settimeout---target:',t_target) // <- HTMLElement
                    }, 0)

参考相关资料:

1 https://github.com/vuejs/vue/issues/7027

2 https://claude.ai/

相关推荐
前端Hardy16 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891119 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
疯狂的沙粒3 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪4 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背4 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M4 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc4 小时前
学习electron
javascript·学习·electron
想自律的露西西★4 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
白墨阳4 小时前
vue3:瀑布流
前端·javascript·vue.js