首頁 > 軟體

Chrome 89 更新事件觸發順序,導致99%的文章都錯了(包括MDN)

2021-05-27 20:30:21

「來源: |程式設計微刊 ID:wangxiaoting678」

大家好,我是秋風。

嗯...我又來了,這次又是在...楠溪和的討論中產生的問題。

那事情是怎麼樣的呢?

起因

最近楠溪在看事件相關的文章,然後就跑來和我討論說以下程式碼的執行效果和網上的文章不一致,程式碼如下:

<div><button>123</button></div><script>var btn = document.querySelector('button');var div = document.querySelector('div'); btn.addEventListener('click', function () {console.log('bubble', 'btn'); }, false); btn.addEventListener('click', function () {console.log('capture', 'btn'); }, true); div.addEventListener('click', function () {console.log('bubble', 'div'); }, false); div.addEventListener('click', function () {console.log('capture', 'div'); }, true);</script>以上是一段很簡單的事件註冊的程式碼,然後我們點選 button。

先不看結果,思考一下。

然後我們來看看結果

對於絕大多數前端老鳥來說,會脫口而出地說出以下順序。

capture divbubble btncapture btnbubble div探索

但是不管是MDN,還是網上大多數的教程而言說的都是這個順序。

對於這個現象,我感到很迷惑,我依稀記得,在幾個月前,Chrome 還不是這樣的行為,盲猜是不是因為 Chrome 版本的問題呢?

以上動圖的 Chrome 版本是 90.0.4430.212

因此我找了個 Chrome 版本為 84.0.4109.0 進行測試。

果然是版本的問題,但是事情的追蹤依然很難,由於搜尋了規範以及查了谷歌上的一些資料,並沒有很好地幫助我解決這個疑惑,我不確定是因為 Chrome 引入的 bug 還是出現了什麼問題。

因此我就向 chromium 報告了這個問題。

最終在 Chrome 開發人員的幫助下,找到了這兩個討論

https://github.com/whatwg/dom/issues/685

https://github.com/whatwg/dom/issues/746

在上述 issues 中可以看到, 起因是在 https://bugs.webkit.org/show_bug.cgi?id=174288 中,有人指出,在 webkit 中當前的事件模型,會導致含有 Shadow DOM 的情況下,子元素的捕獲事件會優先於父元素的捕獲事件觸發。

而在舊模型中,一旦達到 AT_TARGET ,所有註冊的監聽器就將按照順序被觸發,而不管他們是否被標記為捕獲。由於 Shadow DOM 會創建多個 targets ,導致了事件執行順序的錯誤。

而上述問題在 Gecko (Mozilla Firefox 的排版引擎)卻運行正常(先捕獲再冒泡)。為此 whatwg 提出了一個新的模型結構來解決這個問題。

結論

所有的疑問到此都迎刃而解了,到現在為主我們梳理一下我們的問題。

而這個版本分界線是在 Chrome 89.0.4363.0 和 89.0.4358.0。

而 Chrome 89.0.4363.0 是在 2020-12-22 釋出的,也就是最近幾個月的事情,因此近幾個月如果你的Chrome 更新了就會遇到和我一樣的現象。

在 Chrome 89.0.4363.0 以及之後版本中,目標元素的觸發事件順序不再按照註冊順序觸發而是按照先捕獲再冒泡的形式依次執行!

然後我們再來看看這樣修改會給我們帶來怎麼樣的影響。

首先我們要明確是的,網上以前的大部分文章已經不適用於當下的 Chrome 新版本了!如果我們業務中有依賴相關的事件觸發順序,請仔細檢查!舉個

<div><button>123</button></div><script>var a = [];var btn = document.querySelector('button');var div = document.querySelector('div'); btn.addEventListener('click', function () {console.log('bubble', 'btn'); a.push(1); }, false); btn.addEventListener('click', function () {console.log('capture', 'btn'); a.push(2); }, true); div.addEventListener('click', function () {console.log('bubble', 'div'); }, false); div.addEventListener('click', function () {console.log('capture', 'div'); }, true);</script>在新版本中,當 button 元素被點選後,最終 a 的結果為 [2,1],而在舊版本中,這個結果則為 [1,2]。

那對於現階段一些線上程式碼改如何改造呢?

改進方案

那麼現在我們無法控制使用者使用哪個版本的瀏覽器,該如何解決這個問題而來避免遇到線上問題呢?

其實很簡單。

我們只需要將所有目標元素程式碼的順序都按照先書寫捕獲事件程式碼,再書寫冒泡事件程式碼,就可以相容本次的更新。

思考

所有的事情都不是一成不變的,不管是對於一些相對官方的文章或者教程我們都要抱以懷疑的態度,相信我們所看到的。也許我這篇的言論在多年之後也會是一個錯誤示例,但是是對當下問題的一個記錄。本文也還有很多不足之處,如果有問題請在評論中指出。

參考資料

https://chromium.cypress.io/

https://github.com/whatwg/dom/issues/685

https://bugs.webkit.org/show_bug.cgi?id=174288

https://github.com/whatwg/dom/issues/746

https://dom.spec.whatwg.org/#dispatching-events


IT145.com E-mail:sddin#qq.com