廢話不多說(主要文筆比較差),直接上程式碼
一個簡單的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();
輸出結果如圖
是不是有點奇怪,上面明明說.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/