1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        事件循環(huán)機(jī)制的那些事

        共 5346字,需瀏覽 11分鐘

         ·

        2020-12-31 22:24

        作者 | 可達(dá)呱呱鴨

        寫(xiě)在前面

        校招筆試中我們經(jīng)常會(huì)遇到這樣一個(gè)問(wèn)題:

        1. 寫(xiě)出下面代碼的運(yùn)行結(jié)果:

        2. console.log('1');

        3. setTimeout(function () {

        4. ? ?console.log('2');

        5. }, 0);

        6. setTimeout(function () {

        7. ? ?console.log('3');

        8. }, 2000);

        9. console.log('4');

        看完這道題的我自信的寫(xiě)下了答案:?1,2,4,3

        面試官:為什么是這個(gè)答案?

        我:首先打印?1,遇到定時(shí)器,等待時(shí)間為0,所以打印?2,又遇到一個(gè)定時(shí)器,等待時(shí)間為2秒,所以先打印?4,兩秒后打印?3。

        然后就......

        與這段代碼相關(guān)的知識(shí)點(diǎn)就是JavaScript事件循環(huán)機(jī)制,下面將從有關(guān)的基本概念出發(fā),先了解了相關(guān)的概念,才能更好的理解事件循環(huán)的機(jī)制原理。以下都是自己的個(gè)人理解,如有不正確的地方,歡迎大家在評(píng)論區(qū)拍磚。

        線程與進(jìn)程

        關(guān)于線程與進(jìn)程的關(guān)系可以用下面的圖進(jìn)行說(shuō)明:

        • 進(jìn)程好比圖中的工廠,有單獨(dú)的專屬自己的工廠資源。

        • 線程好比圖中的工人,多個(gè)工人在一個(gè)工廠中協(xié)作工作,工廠與工人是?1:n的關(guān)系。

        • 多個(gè)工廠之間獨(dú)立存在。

        而官方的說(shuō)法是:

        • 進(jìn)程是?CPU資源分配的最小單位。

        • 線程是?CPU調(diào)度的最小單位。

        從更直觀的例子來(lái)看,可以打開(kāi)任務(wù)管理器查看,第一個(gè)?tab便是進(jìn)程列表,每一個(gè)進(jìn)程占有的?CPU資源和內(nèi)存資源的比例很直觀的展示出來(lái)。

        為什么js是單線程

        初學(xué)計(jì)算機(jī)語(yǔ)言的時(shí)候,無(wú)論是?CC++還是?JAVA,都是支持多線程,偏偏?JavaScript是單線程,不支持多線程,這也跟?JavaScript的作用有關(guān),都知道?JavaScript是主要運(yùn)行在瀏覽器的腳本語(yǔ)言,最終操作的是頁(yè)面的?DOM結(jié)構(gòu),當(dāng)兩個(gè)?JavaScript腳本同時(shí)修改頁(yè)面的同一個(gè)?DOM節(jié)點(diǎn)時(shí),瀏覽器該執(zhí)行哪個(gè)呢?所以當(dāng)時(shí)設(shè)計(jì)?JavaScript時(shí),便要求當(dāng)前修改操作完成后方可進(jìn)行下一步修改操作。

        瀏覽器是支持多進(jìn)程

        同樣我們打開(kāi)瀏覽器的任務(wù)管理器,以下圖為例:

        瀏覽器的每一個(gè)?tab頁(yè)都是一個(gè)進(jìn)程,有對(duì)應(yīng)的內(nèi)存占用空間、?CPU使用量以及進(jìn)程ID。 新打開(kāi)一個(gè)?tab頁(yè)時(shí),都會(huì)新建一個(gè)進(jìn)程,所以就有一個(gè)?tab頁(yè)對(duì)應(yīng)一個(gè)進(jìn)程的說(shuō)法,但是這種說(shuō)法又是錯(cuò)誤的,因?yàn)闉g覽器有自己的優(yōu)化機(jī)制,當(dāng)我們打開(kāi)多個(gè)空白的?tab頁(yè)時(shí),瀏覽器會(huì)將這多個(gè)空白頁(yè)的進(jìn)程合并為一個(gè),從而減少了進(jìn)程的數(shù)量個(gè)數(shù)。

        瀏覽器內(nèi)核

        瀏覽器內(nèi)核中有多個(gè)進(jìn)程在同步工作,今天涉及到的瀏覽器的進(jìn)程主要包括以下進(jìn)程:

        • Browser 進(jìn)程

        主進(jìn)程,主要負(fù)責(zé)頁(yè)面管理以及管理其他進(jìn)程的創(chuàng)建和銷毀等,常駐的線程有:

        • GUI渲染線程

        • JS引擎線程

        • 事件觸發(fā)線程

        • 定時(shí)器觸發(fā)線程

        • HTTP請(qǐng)求線程

        GUI渲染線程

        • 主要負(fù)責(zé)頁(yè)面的渲染,解析HTML、CSS,構(gòu)建DOM樹(shù),布局和繪制等。

        • 當(dāng)界面需要重繪或者由于某種操作引發(fā)回流時(shí),將執(zhí)行該線程。

        • 該線程與JS引擎線程互斥,當(dāng)執(zhí)行JS引擎線程時(shí),GUI渲染會(huì)被掛起,當(dāng)任務(wù)隊(duì)列空閑時(shí),JS引擎才會(huì)去執(zhí)行GUI渲染。

        JS引擎線程

        • 該線程當(dāng)然是主要負(fù)責(zé)處理?JavaScript腳本,執(zhí)行代碼。

        • 也是主要負(fù)責(zé)執(zhí)行準(zhǔn)備好待執(zhí)行的事件,即定時(shí)器計(jì)數(shù)結(jié)束,或者異步請(qǐng)求成功并正確返回時(shí),將依次進(jìn)入任務(wù)隊(duì)列,等待?JS引擎線程的執(zhí)行。

        • 當(dāng)然,該線程與?GUI渲染線程互斥,當(dāng)?JS引擎線程執(zhí)行?JavaScript腳本時(shí)間過(guò)長(zhǎng),將導(dǎo)致頁(yè)面渲染的阻塞。

        事件觸發(fā)線程

        • 主要負(fù)責(zé)將準(zhǔn)備好的事件交給?JS引擎線程執(zhí)行。

        • 比如?setTimeout定時(shí)器計(jì)數(shù)結(jié)束,?ajax等異步請(qǐng)求成功并觸發(fā)回調(diào)函數(shù),或者用戶觸發(fā)點(diǎn)擊事件時(shí),該線程會(huì)將整裝待發(fā)的事件依次加入到任務(wù)隊(duì)列的隊(duì)尾,等待?JS引擎線程的執(zhí)行。

        定時(shí)器觸發(fā)線程

        • 顧名思義,負(fù)責(zé)執(zhí)行異步定時(shí)器一類的函數(shù)的線程,如:?setTimeout,setInterval

        • 主線程依次執(zhí)行代碼時(shí),遇到定時(shí)器,會(huì)將定時(shí)器交給該線程處理,當(dāng)計(jì)數(shù)完畢后,事件觸發(fā)線程會(huì)將計(jì)數(shù)完畢后的事件加入到任務(wù)隊(duì)列的尾部,等待JS引擎線程執(zhí)行。

        HTTP請(qǐng)求線程

        • 顧名思義,負(fù)責(zé)執(zhí)行異步請(qǐng)求一類的函數(shù)的線程,如:?Promise,anxiosajax等。

        • 主線程依次執(zhí)行代碼時(shí),遇到異步請(qǐng)求,會(huì)將函數(shù)交給該線程處理,當(dāng)監(jiān)聽(tīng)到狀態(tài)碼變更,如果有回調(diào)函數(shù),事件觸發(fā)線程會(huì)將回調(diào)函數(shù)加入到任務(wù)隊(duì)列的尾部,等待JS引擎線程執(zhí)行。

        多個(gè)線程之間配合工作,各司其職。

        • Render 進(jìn)程

        瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核),主要負(fù)責(zé)頁(yè)面的渲染、JS執(zhí)行以及事件的循環(huán)。

        同步任務(wù)和異步任務(wù)

        • 同步任務(wù)?即可以立即執(zhí)行的任務(wù),例如?console.log()?打印一條日志、聲明一個(gè)變量或者執(zhí)行一次加法操作等。

        • 異步任務(wù)?相反不會(huì)立即執(zhí)行的事件任務(wù)。異步任務(wù)包括宏任務(wù)微任務(wù)(后面會(huì)進(jìn)行解釋~)。

        • 常見(jiàn)的異步操作:

          • Ajax

          • DOM的事件操作

          • setTimeout

          • Promise的then方法

          • Node的讀取文件

        下圖給出了同步任務(wù)與異步任務(wù)的執(zhí)行流程:

        • ?就像是一個(gè)容器,任務(wù)都是在棧中執(zhí)行。

        • 主線程?就像是操作員,負(fù)責(zé)執(zhí)行棧中的任務(wù)。

        • 任務(wù)隊(duì)列?就像是等待被加工的物品。

        • 異步任務(wù)完成注冊(cè)后會(huì)將回調(diào)函數(shù)加入任務(wù)隊(duì)列等待主線程執(zhí)行。

        • 執(zhí)行棧中的同步任務(wù)執(zhí)行完畢后,會(huì)查看并讀取任務(wù)隊(duì)列中的事件函數(shù),于是任務(wù)隊(duì)列的函數(shù)結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開(kāi)始執(zhí)行。

        那么任務(wù)到底是如何入棧和出棧的呢?可以用一小段代碼進(jìn)行解釋。

        入棧與出棧

        以下面的代碼為例:

        1. console.log(1);

        2. function fn1 () {

        3. ? ?console.log(2);

        4. }

        5. function fn2 () {

        6. ? ?console.log(3);

        7. ? ?fn1();

        8. }

        9. setTimeout (function () {

        10. ? ?console.log(4);

        11. }, 2000);

        12. fn2();

        13. console.log(5);

        所以上面代碼運(yùn)行的結(jié)果為:1,3,2,5,4。

        宏任務(wù)和微任務(wù)

        異步任務(wù)分為宏任務(wù)和微任務(wù),宏任務(wù)隊(duì)列可以有多個(gè),微任務(wù)隊(duì)列只有一個(gè)。

        宏任務(wù)和微任務(wù)的執(zhí)行方式在瀏覽器和?Node?中有差異。

        宏任務(wù)(macrotask)

        script(全局任務(wù)),?setTimeout,?setInterval,?setImmediate,?I/O,?UI rendering

        微任務(wù)(macrotask)

        process.nextTick,?Promise.then(),?Object.observe,?MutationObserver

        在微任務(wù)中 process.nextTick 優(yōu)先級(jí)高于Promise

        當(dāng)一個(gè)異步任務(wù)入棧時(shí),主線程判斷該任務(wù)為異步任務(wù),并把該任務(wù)交給異步處理模塊處理,當(dāng)異步處理模塊處理完打到觸發(fā)條件時(shí),根據(jù)任務(wù)的類型,將回調(diào)函數(shù)壓入任務(wù)隊(duì)列。

        • 如果是宏任務(wù),則新增一個(gè)宏任務(wù)隊(duì)列,任務(wù)隊(duì)列中的宏任務(wù)可以有多個(gè)來(lái)源。

        • 如果是微任務(wù),則直接壓入微任務(wù)隊(duì)列。

        所以上圖的任務(wù)隊(duì)列可以繼續(xù)細(xì)化一下:

        那么當(dāng)棧為空時(shí),宏任務(wù)和微任務(wù)的執(zhí)行機(jī)制又是什么呢?

        Event Loop

        到這里,除了上面的問(wèn)題,我們已經(jīng)把事件循環(huán)的最基本的處理方式搞清楚了,但具體到異步任務(wù)中的宏任務(wù)和微任務(wù),還沒(méi)有弄明白。我們可以先順一遍執(zhí)行機(jī)制:

        • 從全局任務(wù)?script開(kāi)始,任務(wù)依次進(jìn)入棧中,被主線程執(zhí)行,執(zhí)行完后出棧。

        • 遇到異步任務(wù),交給異步處理模塊處理,對(duì)應(yīng)的異步處理線程處理異步任務(wù)需要的操作,例如定時(shí)器的計(jì)數(shù)和異步請(qǐng)求監(jiān)聽(tīng)狀態(tài)的變更。

        • 當(dāng)異步任務(wù)達(dá)到可執(zhí)行狀態(tài)時(shí),事件觸發(fā)線程將回調(diào)函數(shù)加入任務(wù)隊(duì)列,等待棧為空時(shí),依次進(jìn)入棧中執(zhí)行。

        到這問(wèn)題就來(lái)了,當(dāng)異步任務(wù)進(jìn)入棧執(zhí)行時(shí),是宏任務(wù)還是微任務(wù)呢?

        • 由于執(zhí)行代碼入口都是全局任務(wù)?script,而全局任務(wù)屬于宏任務(wù),所以當(dāng)棧為空,同步任務(wù)任務(wù)執(zhí)行完畢時(shí),會(huì)先執(zhí)行微任務(wù)隊(duì)列里的任務(wù)。

        • 微任務(wù)隊(duì)列里的任務(wù)全部執(zhí)行完畢后,會(huì)讀取宏任務(wù)隊(duì)列中拍最前的任務(wù)。

        • 執(zhí)行宏任務(wù)的過(guò)程中,遇到微任務(wù),依次加入微任務(wù)隊(duì)列。

        • ??蘸?,再次讀取微任務(wù)隊(duì)列里的任務(wù),依次類推。

        實(shí)例解析

        回到最開(kāi)始的那段代碼,現(xiàn)在我們可以一步一步的看一下執(zhí)行順序。

        1. console.log('1');

        2. setTimeout(function () {

        3. ? ?console.log('2');

        4. }, 0);

        5. setTimeout(function () {

        6. ? ?console.log('3');

        7. }, 2000);

        8. console.log('4');

        • 從全局任務(wù)入口,首先打印日志?1,

        • 遇到宏任務(wù)?setTimeout,交給異步處理模塊,我們暫且先記為?setTimeout1,

        • 再次遇到宏任務(wù)?setTimeout,交給異步處理模塊,我們暫且先記為?setTimeout2,

        • 順序執(zhí)行,打印日志?4,

        • 此時(shí)同步任務(wù)已執(zhí)行完畢,讀取宏任務(wù)隊(duì)列的任務(wù),先執(zhí)行?setTimeout1的回調(diào)函數(shù),因?yàn)槎〞r(shí)器的等待時(shí)間為?0秒,所以會(huì)直接輸出?2,但是?W3C在?HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求?setTimeout中低于?4ms的時(shí)間間隔算為?4ms

        • 由于瀏覽器在執(zhí)行以上三步時(shí),并未耗時(shí)很久,所以當(dāng)宏任務(wù)?setTimeout1執(zhí)行完時(shí),?setTimeout2的等待時(shí)間并未結(jié)束,所以在?2后打印日志?3,實(shí)際上并未等待2秒。

        下面我們可以再看一個(gè)實(shí)例:

        1. setTimeout(function () {

        2. ? ?console.log(1);

        3. ? ?Promise.resolve().then(function () {

        4. ? ? ? ?console.log(2);

        5. ? ?});

        6. }, 0);

        7. setTimeout(function () {

        8. ? ?console.log(3);

        9. }, 0);

        10. Promise.resolve().then(function () {

        11. ? ?console.log(4);

        12. });

        13. console.log(5);

        當(dāng)代碼中遇到了異步請(qǐng)求的事件,又該如何執(zhí)行,根據(jù)上面總結(jié)的執(zhí)行機(jī)制,又該得到什么樣的結(jié)果?

        第一輪循環(huán)

        • 同樣從全局任務(wù)入口,遇到宏任務(wù)?setTimeout,交給異步處理模塊,我們暫且先記為?setTimeout1,由于等待時(shí)間為?0,直接加入宏任務(wù)隊(duì)列。

        • 再次遇到宏任務(wù)?setTimeout,交給異步處理模塊,我們暫且先記為?setTimeout2,同樣直接加入宏任務(wù)隊(duì)列。

        • 遇到微任務(wù)?then(),加入微任務(wù)隊(duì)列。

        • 最后遇到打印語(yǔ)句,直接打印日志?5。

        第一輪循環(huán)結(jié)束后,可以畫(huà)出下圖:

        第二輪循環(huán)

        • 棧空后,先執(zhí)行微任務(wù)隊(duì)列中的?then()方法,輸出?4,此時(shí)微任務(wù)隊(duì)列為空。

        • 讀取宏任務(wù)隊(duì)列的最靠前的任務(wù)?setTimeout1

        • 先直接執(zhí)行打印語(yǔ)句,打印日志?1,又遇到微任務(wù)?then(),加入微任務(wù)隊(duì)列。第二輪循環(huán)結(jié)束。

        第三輪循環(huán)

        • 先執(zhí)行微任務(wù)隊(duì)列中的?then()方法,輸出?2,此時(shí)微任務(wù)隊(duì)列為空。

        • 繼續(xù)讀取宏任務(wù)隊(duì)列的最靠前的任務(wù)?setTimeout2。

        • 直接執(zhí)行打印語(yǔ)句,打印日志?3。第三輪循環(huán)結(jié)束,執(zhí)行完畢。

        最后我們是我們的boss,歡迎大家在評(píng)論區(qū)留言寫(xiě)出自己心中的那個(gè)正確答案。

        1. console.log('1');

        2. setTimeout(function() {

        3. ? ?console.log('2');

        4. ? ?new Promise(function(resolve) {

        5. ? ? ? ?console.log('3');

        6. ? ? ? ?resolve();

        7. ? ?}).then(function() {

        8. ? ? ? ?console.log('4')

        9. ? ?})

        10. })

        11. new Promise(function(resolve) {

        12. ? ? ? ?console.log('5');

        13. ? ? ? ?resolve();

        14. }).then(function() {

        15. ? ? ? ?console.log('6')

        16. })

        17. setTimeout(function() {

        18. ? ?console.log('7');

        19. })

        20. setTimeout(function() {

        21. ? ?console.log('8');

        22. ? ?new Promise(function(resolve) {

        23. ? ? ? ?console.log('9');

        24. ? ? ? ?resolve();

        25. ? ?}).then(function() {

        26. ? ? ? ?console.log('10')

        27. ? ?})

        28. })

        29. new Promise(function(resolve) {

        30. ? ?console.log('11');

        31. ? ?resolve();

        32. }).then(function() {

        33. ? ?console.log('12')

        34. })

        35. console.log('13');


        瀏覽 54
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            97黄色网 | 日韩色情中文字幕二区 | 2025中文字幕精品无码 | 逼逼骚 | 中国少妇精品 OOO喷水 | 久久久久久久免费视频 | 国产男女网站 | jizz成熟丰满韩国女视频 | 一女多男高h肉文 | 夫妻一级生活片 |