javascript
JavaScript事件循环探索
一直對js的事件循環(huán)不是很清晰,最近看了JavaScript忍者秘籍的第13章后,有了一些感悟,特此總結(jié)一下,分享給大家。
單線程
眾所周知,JavaScript是單線程執(zhí)行模型,同一時刻只能執(zhí)行一個代碼片段,一個任務(wù)開始后知道運(yùn)行完成,不會被其他任務(wù)中斷。當(dāng)一個任務(wù)花費(fèi)的時間很長的話,用戶就會明顯的感覺到卡頓。瀏覽器為了解決這個問題引入了事件循環(huán)的概念(Event Loop)。
事件循環(huán)
事件循環(huán)具有至少兩個隊(duì)列處理任務(wù)。任務(wù)分為兩類,宏任務(wù)(macro-task)和微任務(wù)(micro-task)。
1.宏任務(wù)代表一個個離散、獨(dú)立的工作單元,運(yùn)行完之后,瀏覽器可以繼續(xù)其他的調(diào)度。包括:創(chuàng)建文檔對象,解析HTML,執(zhí)行JavaScript,以及各種事件…… 2.微任務(wù)是更小的任務(wù),主要用戶更新應(yīng)用程序的狀態(tài),必須在瀏覽器任務(wù)繼續(xù)執(zhí)行其他任務(wù)之前執(zhí)行。微任務(wù)需要盡可能快地通過異步方式執(zhí)行,同時不能產(chǎn)生全新的微任務(wù)。包括promise、回調(diào)函數(shù)、DOM發(fā)生變化……僅包含宏任務(wù)
// 主線程JavaScript運(yùn)行15ms btn1.addEventListener('click', function() {運(yùn)行 8ms}, false); btn2.addEventListener('click', function() {運(yùn)行 5ms}, false);現(xiàn)在假設(shè)主線程運(yùn)行15ms, 在第5ms單擊btn1,在第12ms的時候單擊btn2。基于單線程執(zhí)行模型,單擊按鈕之后不會立即執(zhí)行對應(yīng)的處理函數(shù),因?yàn)橐粋€任務(wù)一旦開始就不會被另一個任務(wù)中斷。因此,在主線程執(zhí)行的15ms期間,按鈕的單擊處理函數(shù)放入隊(duì)列。當(dāng)主線程執(zhí)行完成也就是15ms之后,程序開始處理微任務(wù),因?yàn)楫?dāng)前不存在微任務(wù),跳過此步驟,開始執(zhí)行更新UI。
之后進(jìn)入第二次循環(huán),也就是開始執(zhí)行btn1的處理函數(shù),需要運(yùn)行8ms,btn2處理函數(shù)在隊(duì)列中等待。當(dāng)btn1處理函數(shù)執(zhí)行完之后,瀏覽器檢查微任務(wù)是否存在和是否更新UI,刪除任務(wù)隊(duì)列里的btn1的處理函數(shù)。
最后進(jìn)入第三次循環(huán),開始執(zhí)行btn2的處理函數(shù),需要運(yùn)行5ms,處理函數(shù)執(zhí)行完之后,檢查微任務(wù)和是否需要更新UI,刪除任務(wù)隊(duì)列里的btn2的處理函數(shù),最終任務(wù)隊(duì)列為空,循環(huán)結(jié)束。
同時含有宏任務(wù)和微任務(wù)
// 主線程JavaScript運(yùn)行15ms btn1.addEventListener('click', function() {Promise.resolve().then(() => {運(yùn)行 4ms });運(yùn)行 8ms }, false); btn2.addEventListener('click', function() {運(yùn)行 5ms}, false);本例中在btn1的事件處理函數(shù)里增加了一個立即兌現(xiàn)的Promise,需要運(yùn)行4ms。
現(xiàn)在代碼的執(zhí)行順序?yàn)?#xff1a;
計(jì)時器
// 主線程JavaScript運(yùn)行18ms setTimeout(function() {運(yùn)行6ms; }, 10); setInterval(function() {運(yùn)行8ms; }, 10); btn1.addEventListener('click', function() {運(yùn)行 10ms}, false);?代碼的執(zhí)行過程是什么呢?
現(xiàn)在我們想象一下主線程代碼需要運(yùn)行18ms,在第6ms的時候用戶點(diǎn)擊了按鈕,在第10ms延遲計(jì)時器到期,間隔計(jì)時器第一次觸發(fā)。
我們知道一個任務(wù)一旦開始執(zhí)行,就無法被其他任務(wù)中斷。所以,6ms將事件處理函數(shù)加入隊(duì)列,10ms分別將延遲計(jì)時器和間隔計(jì)時器回調(diào)放入隊(duì)列。運(yùn)行到18m主線程執(zhí)行完畢,檢查微任務(wù)隊(duì)列和更新UI,進(jìn)入下一個時間循環(huán)。開始執(zhí)行btn1事件回調(diào),運(yùn)行10ms,這時候在btn1事件回調(diào)運(yùn)行的過程中,間隔計(jì)時器第二次到期,但是任務(wù)隊(duì)列里面已經(jīng)有一個間隔計(jì)時器處理函數(shù),所以忽略這個處理函數(shù)。btn1事件回調(diào)運(yùn)行結(jié)束,檢查微任務(wù)隊(duì)列和更新UI,進(jìn)入下一個事件循環(huán)。開始執(zhí)行延遲計(jì)時器處理函數(shù),運(yùn)行6ms,在這個過程中間隔計(jì)時器第三次到期,但是由于任務(wù)隊(duì)列已經(jīng)有了處理函數(shù),繼續(xù)忽略。延遲計(jì)時器處理函數(shù)運(yùn)行完畢,檢查微任務(wù)隊(duì)列和更新UI,進(jìn)入下一個事件循環(huán)。現(xiàn)在開始執(zhí)行間隔計(jì)時器處理函數(shù),運(yùn)行8ms,在這期間間隔計(jì)時器第四次到期,這時候任務(wù)隊(duì)列里沒有處理函數(shù),所以將這次的處理函數(shù)放入任務(wù)隊(duì)列,間隔定時器處理函數(shù)運(yùn)行完成,檢查微任務(wù)隊(duì)列和更新UI,進(jìn)入下一個事件循環(huán),然后重復(fù)運(yùn)行間隔定時器……
通過以上的執(zhí)行過程我們發(fā)現(xiàn),我們只能控制計(jì)時器何時被加入隊(duì)列,而無法控制何時執(zhí)行。
最后,JavaScript的事件循環(huán)是這門語言非常重要的基礎(chǔ),由于我水平有限以上只是簡單總結(jié)了一下它的執(zhí)行過程。大家可以深入研究一下Nodejs的事件循環(huán)
總結(jié)
以上是生活随笔為你收集整理的JavaScript事件循环探索的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1.7(java学习笔记)package
- 下一篇: Spark性能优化:资源调优篇