click事件在什么时候出发_剖析setTimeout和click点击事件的触发顺序
下面是一段非常簡單的JavaScript代碼
dianji
setTimeout(function () {
alert('timer handler')
}, 2000)
function test () {
document.addEventListener('click', function (e) {
alert('click handler')
}, false)
var startTime = new Date()
while ((new Date()).getTime() - startTime < 5000){}
}
但是當你點擊這個按鈕時,產生的效果可能會讓你有些困惑。下面我們來看下:
頁面打開2s內點擊一次按鈕
這段JavaScript代碼當你在頁面打開2s時間內點擊一次按鈕,效果是這樣的:
頁面卡住大約5s鐘
大約5s后彈出 click handler
點擊彈窗確認后,彈出 timer handler
當你繼續再次點擊頁面中的按鈕,此時依次發生:
頁面卡住大約5s鐘
大約5s后彈出 click handler 一次!
點擊確認后又彈出 click handler 一次。
如果繼續點擊button按鈕,會出現同樣的效果,且 click handler 彈出的次數會依次增加
分析
分析這段代碼來看,點擊按鈕后,代碼執行會進入 test 函數, test函數中首先對 document 對象綁定上了一個 click 事件。然后執行了一個5s的死循環。
此時頁面卡住就是因為這個死循環
ar startTime = new Date()
while ((new Date()).getTime() - startTime < 5000){}
這個死循環會導致js阻塞在這里. 在這5s時間內,2s的定時器其實在第2秒的時候已經定時完成,并把這個完成的事件放入到了任務隊列中;而你在2秒之前點擊的按鈕這個click事件也被瀏覽器放入一個dom事件隊列等待執行。
當5s死循環的時間過去,js引擎開始變成空閑,此時點擊按鈕觸發的這個test處理器執行完畢,js引擎便從事件隊列中取出 click 事件進行執行,當前元素沒有訂閱click那么就冒泡到訂閱了該事件的document進行執行。(會繼續冒泡到document。(本質上冒泡其實是: 瀏覽器取出dom事件中的click事件,然后從target元素開始往上找 看下是否整個網頁中還有元素訂閱了這個click事件)
由于在剛剛test函數執行期間,document對象上綁定上了 click 的監聽,所以此時冒泡上來的 click 會觸發document對象上的 click事件處理器, 因此彈出了 click handler.
當這個 click 冒泡完畢,所有的訂閱者訂閱的處理器都被完全處理完,js線程再次空閑,此時去查看任務隊列中的任務,發現有個2s定時器的任務已經執行完畢,js開始執行定時器的回調函數,所以彈出了 time handler
當你第二次點擊按鈕,再次觸發了 test 函數。 此時test函數內還是做了同樣的事情,但是之前document上已經綁定了一個click的handler函數,所以第二次執行 test函數,會讓 document對象的click處理器變成2個。 因此第二次點擊按鈕 click handler 會彈出2次
頁面打開2s內點擊一次按鈕,然后第3s時點擊頁面空白處2次
這樣操作的效果是這樣的:
頁面卡住大約5s鐘
大約5s后彈出 'click handler'
彈出 'click handler' 第二次
彈出 'click handler' 第三次
彈出 'time handler'
分析
第2點之所以出現在第5點之前,在上文我們已經講過原因了---總之,基本上是因為click觸發的時刻確實就比timer觸發的早,肯定要等click的handler都處理完再執行timer處理器。
但至于第3、4點為什么出現在5之前呢?這個跟2出現在5之前的原因就不一樣了,因為用戶在頁面上的第二次和第三次點擊是在2s鐘之后了,此時timer定時器肯定已經完成了,但是觸發 click handler 依然在 timer handler 之前。 這是為什么呢?
這主要是因為js獲取任務來執行時, 點擊事件的任務隊列 要優先于 timer事件的任務隊列。 具體可參考我的另外一篇文章 瀏覽器的單線程機制和事件循環
在頁面卡住的5s時間內,用戶在頁面上點擊的2次事件會放入比timer更優先的一個macroTask任務隊列。由于js空閑時優先要把click事件這種更優先的macroTask任務執行完,直到任務隊列為空。所以就出現了上面 click handler 要比 timer handler 更早彈出的效果。
心得
js中事件可以注冊多個handler形成handlers. handlers類似于一個處理器的數組。事件觸發后,該事件的handler處理器會被依次執行.
這里舉個跟上面有點區別的例子:假如在某個handler執行的過程中,又給該事件增加了新的handler,那么新增的這個handler不能立即執行。
demo測試代碼:
dianji
function test () {
alert('click handler 1');
/* test函數觸發的過程中,又給按鈕綁定了新的handler; 但本次handlers遍歷執行的過程中,不會執行新加入的這個handler */
/* 因此,首次點擊按鈕, click handler2 不會彈出 */
document.querySelector('#test').addEventListener('click', function (e) {
alert('click handler 2')
}, false);
}
其實這里原理很簡單:因為test元素對象上的事件handlers被觸發執行的時候,類似于把數組拿出來遍歷。你不可能把遍歷數組和修改數組的邏輯同時運行。如:
let a = [1,2,3]
let count = 'x'
a.forEach((item, index) => {
console.log(item)
a.push(count + index)
})
console.log(a)
// 輸出
// 1
// 2
// 3
// [ 1, 2, 3, 'x0', 'x1', 'x2' ]
除非你addEventListener的時候,添加到冒泡的上層元素上。即下面講的第三點。
addEventListener 會給事件不斷增加新的處理器handler
事件處理器handler在執行期間,事件還沒有冒泡。此時還有機會給上層元素綁定事件處理器。
一個事件在冒泡過程中,要等所有訂閱該事件的處理器都處理完畢,js才會去選擇新的任務隊列中的任務來執行。在事件觸發后以及事件的冒泡過程中,會優先執行訂閱了該冒泡事件的處理器,而不會去理會任務隊列。
這一條原理很簡單,只需知道:js在執行同一個dom事件的所有回調處理器的過程是同步的,占用js線程執行的即可。
在事件循環中 microTask 優先于 marcroTask執行,且macroTask中也有不同優先級的隊列,例如dom事件便高于timer。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的click事件在什么时候出发_剖析setTimeout和click点击事件的触发顺序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python表单处理_python fl
- 下一篇: python工业自动化镜头_ELVIS