建設(shè)網(wǎng)站證書西安百度seo
“事件循環(huán)機(jī)制” 和 “宏任務(wù)微任務(wù)” 也是前端面試中??嫉拿嬖囶}了。
首先,要深刻理解這些概念的話,需要回顧一些知識點(diǎn)。
知識點(diǎn)回顧
1、進(jìn)程與線程
進(jìn)程。
程序運(yùn)行需要有它自己的專屬內(nèi)存空間,可以把這塊內(nèi)存空間簡單的理解為進(jìn)程
每個應(yīng)用至少有一個進(jìn)程,進(jìn)程之間相互獨(dú)立,即使要通信,也需要雙方同意。
線程。
線程是CPU的基本調(diào)度單位,是程序執(zhí)行的一個完整流程。
一個進(jìn)程至少有一個線程,所以在進(jìn)程開啟后會自動創(chuàng)建一個線程來運(yùn)行代碼,該線程稱之為 主線程。
如果程序需要同時執(zhí)行多塊代碼,主線程就會開啟更多的線程來執(zhí)行代碼,所以一個進(jìn)程中可以包含多個線程。
簡單總結(jié)一些它們的關(guān)系:
一個進(jìn)程中一般至少有一個運(yùn)行的線程——主線程
一個進(jìn)程中也可以同時運(yùn)行多個線程
多個進(jìn)程之間的數(shù)據(jù)是不能同時直接共享的
那瀏覽器有哪些進(jìn)程與線程呢?
瀏覽器內(nèi)部的工作其實(shí)極為復(fù)雜,它是多進(jìn)程多線程的。且為了避免相互影響,它會自動啟動多個進(jìn)程。
比如,我們可以在瀏覽器任務(wù)管理器查看一下所有進(jìn)程。

其中,最主要的進(jìn)程有:
瀏覽器進(jìn)程
主要負(fù)責(zé)界面顯示、用戶交互、子進(jìn)程管理等。瀏覽器進(jìn)程內(nèi)部會啟動多個線程處理不同的任務(wù)。
網(wǎng)絡(luò)進(jìn)程
負(fù)責(zé)加載網(wǎng)絡(luò)資源。網(wǎng)絡(luò)進(jìn)程內(nèi)部會啟動多個線程來處理不同的網(wǎng)絡(luò)任務(wù)
渲染進(jìn)程
渲染進(jìn)程啟動后,會開啟一個 渲染主線程,主線程負(fù)責(zé)執(zhí)行 HTML、CSS、JS代碼
默認(rèn)情況下,瀏覽器會為每一個標(biāo)簽頁開啟一個新的渲染進(jìn)程,以保證不同的標(biāo)簽頁之間不互相影響。
2、JS是單線程還是多線程?
JS肯定是單線程的。
如果是多線程會發(fā)生什么? 如果JS是多線程,那當(dāng)兩個線程同時對dom進(jìn)行操作,一個是添加事件,一個是刪除dom,要怎么處理? 所以為了避免這種情況,JS選擇只用一個主線程來執(zhí)行代碼以保證一致性。
3、怎么去理解JS的異步?
上面寫到JS是單線程的,它運(yùn)行在瀏覽器的渲染主線程中,且渲染主線程有且僅有一個。 但是它卻有很多任務(wù),比如渲染頁面、執(zhí)行JS都包含在內(nèi)。
那如果不使用異步,而是同步的方式,就極有可能造成主線程的阻塞,那其他任務(wù)就無法執(zhí)行了,一方面消耗時間,另一方面頁面又沒法及時更新,很容易有卡死現(xiàn)象。
所以瀏覽器采用異步方式。 具體做法其實(shí)就是, 比如一些任務(wù)發(fā)生了,假設(shè)現(xiàn)在遇到了計(jì)時器,主線程會將任務(wù)交給其他線程處理,自己立馬結(jié)束這個任務(wù),轉(zhuǎn)而執(zhí)行后續(xù)代碼。 而等其他線程完成后,將事先傳遞的回調(diào)函數(shù)包裝成任務(wù),再加入到消息隊(duì)列的末尾排隊(duì),等待主線程的調(diào)度執(zhí)行。
在這種異步模式下,瀏覽器就可以避免阻塞。
這一段解釋涉及到操作系統(tǒng)的進(jìn)程調(diào)度問題和我們所要理解的事件循環(huán)機(jī)制。暫時看不懂的,可以先往下看。
4、進(jìn)程調(diào)度
進(jìn)程調(diào)度的知識點(diǎn)稍多,比如搶占式調(diào)度,非搶占式調(diào)度,先來先服務(wù),優(yōu)先級調(diào)度等等。
我們這里就簡單介紹一下先來先服務(wù)(FCFS)。
它的算法思想其實(shí)就是從“公平”的角度來考慮的 (我們可以理解成 排隊(duì)買東西,先來排隊(duì)的優(yōu)先買)。所以它的算法規(guī)則,其實(shí)是按照 作業(yè)/進(jìn)程 到達(dá)的先后順序進(jìn)行服務(wù)。 它是一種非搶占式算法 (可以理解成 “不允許你插隊(duì)”),它不會導(dǎo)致 “饑餓”現(xiàn)象(也就是一直輪不到執(zhí)行,苦苦等待),因?yàn)橹灰抨?duì)終有一天會輪到它的。
事件循環(huán)機(jī)制
介紹完一些知識點(diǎn)后,再理解一下主角“事件循環(huán)機(jī)制”
我們在前面說了,瀏覽器會通過渲染主線程去執(zhí)行JS
在最開始的時候,渲染主線程會進(jìn)入一個無限的循環(huán)中
每一次的循環(huán),都會檢查一下消息隊(duì)列中是否存在任務(wù)。 如果存在任務(wù),就取出第一個任務(wù)執(zhí)行,執(zhí)行完一個后進(jìn)入下一次循環(huán); 如果沒有,就進(jìn)入等待態(tài)(休眠)
其他所有線程(包括其他進(jìn)程的線程)可以隨時向消息隊(duì)列添加任務(wù),如果主線程是等待態(tài),會將其喚醒以繼續(xù)循環(huán)拿取任務(wù)執(zhí)行。
這種過程,也就被稱為 事件循環(huán)(消息循環(huán))
在上面的過程中,我們先簡單的將消息隊(duì)列理解成一個隊(duì)列(雖然有具體劃分)。 而隊(duì)列的特性是先進(jìn)先出,比如我們按順序執(zhí)行代碼,分別遇到了 加法任務(wù)、輸出任務(wù)、乘法任務(wù)。 那他們依次入隊(duì),渲染主線程循環(huán)獲取任務(wù)也是按照這個順序去執(zhí)行的
宏任務(wù)和微任務(wù)
在上面為了便于簡單的理解,說是當(dāng)成一個隊(duì)列,其實(shí)不是的。 JS中用來存儲代執(zhí)行回調(diào)函數(shù)的隊(duì)列可以分為2種不同的隊(duì)列,那就是 宏隊(duì)列 和 微隊(duì)列。 顧名思義就是分別用來保存待執(zhí)行的宏任務(wù)和微任務(wù)(回調(diào))。
常見的宏任務(wù)包括:
setTimeout
setInterval
script(整體代碼)
I/O操作
等等
微任務(wù)包括:
Promise
Mutation
等等
既然會劃分成2個隊(duì)列,那肯定是要在JS執(zhí)行時區(qū)別對待它們的。 JS引擎首先必須先執(zhí)行所有的初始化同步任務(wù), 在每次準(zhǔn)備取出第一個宏任務(wù)執(zhí)行前,都要看看有沒有微任務(wù),要一個個取出來執(zhí)行。 當(dāng)該宏任務(wù)執(zhí)行完畢后,會檢查其中的微任務(wù)隊(duì)列,如果沒有,那就直接執(zhí)行下一個宏任務(wù),如果不為空,那就依次執(zhí)行微任務(wù),執(zhí)行完畢再執(zhí)行下一個宏任務(wù)。
所以引入微任務(wù)的初衷是為了解決異步回調(diào)的問題。(其實(shí)我們可以理解為 微任務(wù)的優(yōu)先級比較高,根據(jù)優(yōu)先級調(diào)度算法,調(diào)度時會選優(yōu)先級最高的進(jìn)行調(diào)度執(zhí)行)
即然說到優(yōu)先級,就必須要提一下。
任務(wù)本身是沒有優(yōu)先級的,都是遵循先來先服務(wù)算法。 但是 消息隊(duì)列是有優(yōu)先級的。 也就是上面我們所說的 微隊(duì)列比宏隊(duì)列優(yōu)先級高。所以每執(zhí)行一次宏任務(wù),都要看看有沒有微任務(wù)的存在。
但隨著瀏覽器復(fù)雜的提升,W3C似乎不再采用宏隊(duì)列的說法。 而是至少分為了 延時隊(duì)列、交互隊(duì)列、微隊(duì)列。 (它們優(yōu)先級是從低到高的,微隊(duì)列優(yōu)先級最高)。具體內(nèi)容可能還需要看一下官方解釋。
如果我們想把一個函數(shù)添加到微隊(duì)列,可以這么寫
Promise.resolve().then(函數(shù))
基本的介紹就結(jié)束了,應(yīng)該差不多可以理解這些概念了。接下來可以看一道簡單的題
<h1>Eric is handsome</h1>
<button>change</button><script>var h1 = document.querySelector('h1');var btn = document.querySelector('button');// 死循環(huán)指定時間function delay(duration){var start = Date.now();while(Date.now() - start < duration) {}}btn.onclick = function() {h1.textContent = "Eric真帥";delay(3000);}
</script>
對于以上代碼,當(dāng)我們點(diǎn)擊按鈕后,會發(fā)生什么呢?
實(shí)際上,點(diǎn)擊完按鈕后,需要經(jīng)過3秒,h1的文本才會發(fā)生變化。
因?yàn)閷τ阡秩局骶€程而言,運(yùn)行解析JS代碼以后,會用交互線程去監(jiān)聽按鈕的點(diǎn)擊事件。 (假設(shè)我們在某一個時刻點(diǎn)擊了它,此時消息隊(duì)列中沒有其他任務(wù))
那交互線程會將這個function作為一個任務(wù),假設(shè)記為fn,添加至消息隊(duì)列中。 渲染主線程會被喚醒從而調(diào)用fn任務(wù)。 所以可以執(zhí)行function里面的代碼了。 首先是h1.textContent = “Eric真帥”,fn任務(wù)會產(chǎn)生一個繪制任務(wù)(也就是改變h1文本),那這個繪制任務(wù)就會到消息隊(duì)列中進(jìn)行排隊(duì),此時fn任務(wù)繼續(xù)執(zhí)行到下一行 delay(3000),也就是被阻塞了3秒。3秒后,fn執(zhí)行完畢,進(jìn)行循環(huán),這時候獲取了消息隊(duì)列中的繪制任務(wù),調(diào)度執(zhí)行,文本發(fā)生改變。
如果有幫助的話,可以點(diǎn)贊收藏哦~~~