vue代码 imgLoaded(e) { deb" />

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

vue,image,onload,event,target,null · 浏览次数 : 1

小编点评

这段代码中存在一个关于事件处理和异步执行的问题。在 JavaScript 中,事件处理程序是在事件循环任务队列中异步执行的。在这个例子中,我们有一个 `img` 标签,当图片加载完成时,会触发 `onload` 事件。事件处理程序是 `imgLoaded` 函数,它将 `event` 对象作为参数传递。 在 `imgLoaded` 函数中,我们尝试访问 `event.target`,但发现它始终为 `null`。这可能是因为在 `onload` 事件触发时,`event.target` 引用已经被清除。然而,在控制台中第一次打印 `event.target` 时,它仍然存在。这是因为 JavaScript 引擎在处理 `onload` 事件时,仍然保留了对 `event.target` 的引用,直到事件处理程序执行完毕。 为了避免这个问题,我们可以在事件处理程序中创建一个局部变量来存储 `event.target` 的引用,并在后续代码中使用该变量。这样可以确保 `event.target` 的引用不会在异步执行过程中丢失。 这是一个修改后的代码示例: ```html ``` 通过这种方式,我们可以确保在 `setTimeout` 函数执行时,`event.target` 的引用仍然有效,从而正确地输出目标元素的引用。

正文

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

一个简单的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/

 

与关于vue中image控件,onload事件里,event.target 为null的奇怪问题探讨相似的内容:

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

废话不多说(主要文笔比较差),直接上代码 一个简单的demo,如下 vue代码 imgLoaded(e) { deb

【干货】Vue3 组件通信方式详解

毫无疑问,组件通信是Vue中非常重要的技术之一,它的出现能够使我们非常方便的在不同组件之间进行数据的传递,以达到数据交互的效果。所以,学习组件通信技术是非常有必要的,本文将总结Vue中关于组件通信的八种方式,帮助大家在使用Vue的过程中更加得心应手! 如果文中有不对、疑惑的地方,欢迎在评论区留言指正...

京东云开发者|关于“React 和 Vue 该用哪个”我真的栓Q

一、前言:我全都要 面对当今前端界两座大山一样的主流框架,React和Vue,相信很多小伙伴都或多或少都产生过这样疑问,而这样的问题也往往很让人头疼和犹豫不决: 业务场景中是不是团队用什么我就用什么? 如果选择了其中一个使用,那为什么不用另一个? 这两个框架各有什么优点和无法解决的问题? 最新版本的

将 Vue.js 项目部署至静态网站托管,并开启 Gzip 压缩

摘要:关于使用 Nginx 开启静态网站 Gzip 压缩的教程已经有很多了,但是好像没几个讲怎么在对象存储的静态网站中开启 Gzip 压缩。其实也不复杂,我们一起来看下~ 本文分享自华为云社区《将 Vue.js 项目部署至静态网站托管,并开启 Gzip 压缩》,作者:云存储开发者支持团队。 关于使用

基于SqlSugar的开发框架循序渐进介绍(19)-- 基于UniApp+Vue的移动前端的功能介绍

在之前的SqlSugar系列随笔中,介绍了很多我们关于SqlSugar的开发框架的内容,SqlSugar的开发框架的目的是多前端应用场景,因此其中会包含各种不同的前端应用,前面介绍了基于DevExpress的Winform的前端应用,以及基于Vue3+TypeScript+ElementPlus的BS前端应用,本篇随笔继续介绍SqlSugar的开发框架的另一个前端应用,基于UniApp+Vue+T

『手撕Vue-CLI』获取下载目录

开篇 在上一篇文章中,简单的对 Nue-CLI 的代码通过函数柯里化优化了一下,这一次来实现一个获取下载目录的功能。 背景 在 Nue-CLI 中,我现在实现的是 create 指令,这个指令本质就是首先拿到模板名称和版本号之后,然后去进行下载对应的模板,关于下载那么肯定要面临的问题就是如何下载?下

CAS前后端分离解决方案

CAS前后端分离解决方案 关于CSS服务器的搭建和整合SpringBoot参考:CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记 环境与需求 后端:springboot 前端: vue + element UI 在登录后之后登录状态在系统中自主控制。 问题 当接口在CAS过滤器中时

当你使用Taro时,你需要了解的一些事儿

2017 年 1 月 9 日凌晨,万众期待的微信小程序正式上线,前有跳一跳等爆圈小游戏的带动,后有特殊时期下各类健康码小程序的加持,小程序成为了国内技术圈独树一帜的存在。但随着小程序的迅猛发展,其实在小程序发展的过程中,关于小程序的架构就层出不穷,小程序架构的后面也会绑定一个专属 DSL,如类 React 或者类 Vue。

深入理解 Vue 3 组件通信

在 Vue 3 中,组件通信是一个关键的概念,它允许我们在组件之间传递数据和事件。本文将介绍几种常见的 Vue 3 组件通信方法,包括 props、emits、provide 和 inject、事件总线以及 Vuex 状态管理。 1. 使用 props 和 emits 进行父子组件通信 props

vue-router的使用

vue-router是vue基础工具的重要组成部分。 通过简单的配置路由组件映射关系,可以实现vue页面轻松跳转。 什么是前端路由 它是URL地址与组件之间的对应关系,通常使用Hash地址与组件之间对应。 浏览器的Hash地址就是URL地址中对应的锚链接地址, 如: location.href: 完