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){}
}
但是當(dāng)你點(diǎn)擊這個(gè)按鈕時(shí),產(chǎn)生的效果可能會(huì)讓你有些困惑。下面我們來看下:
頁面打開2s內(nèi)點(diǎn)擊一次按鈕
這段JavaScript代碼當(dāng)你在頁面打開2s時(shí)間內(nèi)點(diǎn)擊一次按鈕,效果是這樣的:
頁面卡住大約5s鐘
大約5s后彈出 click handler
點(diǎn)擊彈窗確認(rèn)后,彈出 timer handler
當(dāng)你繼續(xù)再次點(diǎn)擊頁面中的按鈕,此時(shí)依次發(fā)生:
頁面卡住大約5s鐘
大約5s后彈出 click handler 一次!
點(diǎn)擊確認(rèn)后又彈出 click handler 一次。
如果繼續(xù)點(diǎn)擊button按鈕,會(huì)出現(xiàn)同樣的效果,且 click handler 彈出的次數(shù)會(huì)依次增加
分析
分析這段代碼來看,點(diǎn)擊按鈕后,代碼執(zhí)行會(huì)進(jìn)入 test 函數(shù), test函數(shù)中首先對 document 對象綁定上了一個(gè) click 事件。然后執(zhí)行了一個(gè)5s的死循環(huán)。
此時(shí)頁面卡住就是因?yàn)檫@個(gè)死循環(huán)
ar startTime = new Date()
while ((new Date()).getTime() - startTime < 5000){}
這個(gè)死循環(huán)會(huì)導(dǎo)致js阻塞在這里. 在這5s時(shí)間內(nèi),2s的定時(shí)器其實(shí)在第2秒的時(shí)候已經(jīng)定時(shí)完成,并把這個(gè)完成的事件放入到了任務(wù)隊(duì)列中;而你在2秒之前點(diǎn)擊的按鈕這個(gè)click事件也被瀏覽器放入一個(gè)dom事件隊(duì)列等待執(zhí)行。
當(dāng)5s死循環(huán)的時(shí)間過去,js引擎開始變成空閑,此時(shí)點(diǎn)擊按鈕觸發(fā)的這個(gè)test處理器執(zhí)行完畢,js引擎便從事件隊(duì)列中取出 click 事件進(jìn)行執(zhí)行,當(dāng)前元素沒有訂閱click那么就冒泡到訂閱了該事件的document進(jìn)行執(zhí)行。(會(huì)繼續(xù)冒泡到document。(本質(zhì)上冒泡其實(shí)是: 瀏覽器取出dom事件中的click事件,然后從target元素開始往上找 看下是否整個(gè)網(wǎng)頁中還有元素訂閱了這個(gè)click事件)
由于在剛剛test函數(shù)執(zhí)行期間,document對象上綁定上了 click 的監(jiān)聽,所以此時(shí)冒泡上來的 click 會(huì)觸發(fā)document對象上的 click事件處理器, 因此彈出了 click handler.
當(dāng)這個(gè) click 冒泡完畢,所有的訂閱者訂閱的處理器都被完全處理完,js線程再次空閑,此時(shí)去查看任務(wù)隊(duì)列中的任務(wù),發(fā)現(xiàn)有個(gè)2s定時(shí)器的任務(wù)已經(jīng)執(zhí)行完畢,js開始執(zhí)行定時(shí)器的回調(diào)函數(shù),所以彈出了 time handler
當(dāng)你第二次點(diǎn)擊按鈕,再次觸發(fā)了 test 函數(shù)。 此時(shí)test函數(shù)內(nèi)還是做了同樣的事情,但是之前document上已經(jīng)綁定了一個(gè)click的handler函數(shù),所以第二次執(zhí)行 test函數(shù),會(huì)讓 document對象的click處理器變成2個(gè)。 因此第二次點(diǎn)擊按鈕 click handler 會(huì)彈出2次
頁面打開2s內(nèi)點(diǎn)擊一次按鈕,然后第3s時(shí)點(diǎn)擊頁面空白處2次
這樣操作的效果是這樣的:
頁面卡住大約5s鐘
大約5s后彈出 'click handler'
彈出 'click handler' 第二次
彈出 'click handler' 第三次
彈出 'time handler'
分析
第2點(diǎn)之所以出現(xiàn)在第5點(diǎn)之前,在上文我們已經(jīng)講過原因了---總之,基本上是因?yàn)閏lick觸發(fā)的時(shí)刻確實(shí)就比timer觸發(fā)的早,肯定要等click的handler都處理完再執(zhí)行timer處理器。
但至于第3、4點(diǎn)為什么出現(xiàn)在5之前呢?這個(gè)跟2出現(xiàn)在5之前的原因就不一樣了,因?yàn)橛脩粼陧撁嫔系牡诙魏偷谌吸c(diǎn)擊是在2s鐘之后了,此時(shí)timer定時(shí)器肯定已經(jīng)完成了,但是觸發(fā) click handler 依然在 timer handler 之前。 這是為什么呢?
這主要是因?yàn)閖s獲取任務(wù)來執(zhí)行時(shí), 點(diǎn)擊事件的任務(wù)隊(duì)列 要優(yōu)先于 timer事件的任務(wù)隊(duì)列。 具體可參考我的另外一篇文章 瀏覽器的單線程機(jī)制和事件循環(huán)
在頁面卡住的5s時(shí)間內(nèi),用戶在頁面上點(diǎn)擊的2次事件會(huì)放入比timer更優(yōu)先的一個(gè)macroTask任務(wù)隊(duì)列。由于js空閑時(shí)優(yōu)先要把click事件這種更優(yōu)先的macroTask任務(wù)執(zhí)行完,直到任務(wù)隊(duì)列為空。所以就出現(xiàn)了上面 click handler 要比 timer handler 更早彈出的效果。
心得
js中事件可以注冊多個(gè)handler形成handlers. handlers類似于一個(gè)處理器的數(shù)組。事件觸發(fā)后,該事件的handler處理器會(huì)被依次執(zhí)行.
這里舉個(gè)跟上面有點(diǎn)區(qū)別的例子:假如在某個(gè)handler執(zhí)行的過程中,又給該事件增加了新的handler,那么新增的這個(gè)handler不能立即執(zhí)行。
demo測試代碼:
dianji
function test () {
alert('click handler 1');
/* test函數(shù)觸發(fā)的過程中,又給按鈕綁定了新的handler; 但本次handlers遍歷執(zhí)行的過程中,不會(huì)執(zhí)行新加入的這個(gè)handler */
/* 因此,首次點(diǎn)擊按鈕, click handler2 不會(huì)彈出 */
document.querySelector('#test').addEventListener('click', function (e) {
alert('click handler 2')
}, false);
}
其實(shí)這里原理很簡單:因?yàn)閠est元素對象上的事件handlers被觸發(fā)執(zhí)行的時(shí)候,類似于把數(shù)組拿出來遍歷。你不可能把遍歷數(shù)組和修改數(shù)組的邏輯同時(shí)運(yùn)行。如:
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的時(shí)候,添加到冒泡的上層元素上。即下面講的第三點(diǎn)。
addEventListener 會(huì)給事件不斷增加新的處理器handler
事件處理器handler在執(zhí)行期間,事件還沒有冒泡。此時(shí)還有機(jī)會(huì)給上層元素綁定事件處理器。
一個(gè)事件在冒泡過程中,要等所有訂閱該事件的處理器都處理完畢,js才會(huì)去選擇新的任務(wù)隊(duì)列中的任務(wù)來執(zhí)行。在事件觸發(fā)后以及事件的冒泡過程中,會(huì)優(yōu)先執(zhí)行訂閱了該冒泡事件的處理器,而不會(huì)去理會(huì)任務(wù)隊(duì)列。
這一條原理很簡單,只需知道:js在執(zhí)行同一個(gè)dom事件的所有回調(diào)處理器的過程是同步的,占用js線程執(zhí)行的即可。
在事件循環(huán)中 microTask 優(yōu)先于 marcroTask執(zhí)行,且macroTask中也有不同優(yōu)先級(jí)的隊(duì)列,例如dom事件便高于timer。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的click事件在什么时候出发_剖析setTimeout和click点击事件的触发顺序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python表单处理_python fl
- 下一篇: 光遇安卓服务器维修,《光遇》渠道服更换手