瀏覽器架構(gòu)
- 用戶界面
- 主進程
- 內(nèi)核
- 渲染引擎
- JS 引擎
- 執(zhí)行棧
- 事件觸發(fā)線程
- 消息隊列
- 微任務(wù)
- 宏任務(wù)
- 消息隊列
- 網(wǎng)絡(luò)異步線程
- 定時器線程
瀏覽器是多進程的
每打開一個Tab頁禀梳,就相當于創(chuàng)建了一個獨立的瀏覽器進程蚪黑, 可以在Chrome的任務(wù)管理器
中看到裳瘪。(瀏覽器應(yīng)該也有自己的優(yōu)化機制宿崭,有時候打開多個tab頁后,可以在Chrome任務(wù)管理器中看到舌狗,有些進程被合并了鲁驶。所以每一個Tab標簽對應(yīng)一個進程并不一定是絕對的)
瀏覽器包含哪些進程
Browser進程
- 瀏覽器的主進程准验,協(xié)調(diào)控制其他子進程(創(chuàng)建赎线、銷毀)
- 瀏覽器界面顯示,用戶交互糊饱,前進垂寥、后退、收藏
- 將渲染進程得到的內(nèi)存中的Bitmap另锋,繪制到用戶界面上
- 處理不可見操作滞项,網(wǎng)絡(luò)請求,文件訪問等
GPU進程
- 用于3D繪制等(最多一個)
第三方插件進程
- 每種類型的插件對應(yīng)一個進程夭坪,僅當使用該插件時才創(chuàng)建
渲染進程(瀏覽器內(nèi)核)
- 負責(zé)頁面渲染文判,腳本執(zhí)行,事件處理等
- 默認每個tab頁一個渲染進程
瀏覽器多進程的優(yōu)勢
相比于單進程瀏覽器室梅,多進程有如下優(yōu)點:
- 避免單個page crash影響整個瀏覽器
- 避免第三方插件crash影響整個瀏覽器
- 多進程充分利用多核優(yōu)勢
- 方便使用沙盒模型隔離插件等進程戏仓,提高瀏覽器穩(wěn)定性
缺點:內(nèi)存消耗更大
瀏覽器內(nèi)核(渲染進程)
GUI渲染線程
- 負責(zé)渲染頁面疚宇,解析,構(gòu)建赏殃,布局和繪制
- 頁面需要重繪和回流時敷待,該線程就會執(zhí)行
- 與js引擎線程互斥,防止渲染結(jié)果不可預(yù)期
JS引擎線程
- 負責(zé)處理解析和執(zhí)行javascript腳本程序
- 只有一個JS引擎線程(單線程)
- 與GUI渲染線程互斥嗓奢,防止渲染結(jié)果不可預(yù)期
事件觸發(fā)線程
- 歸屬于瀏覽器而不是JS引擎 讼撒,用來控制事件循環(huán)(鼠標點擊浑厚、setTimeout股耽、ajax等)
- JS引擎執(zhí)行包含事件的代碼塊時,會將對應(yīng)任務(wù)添加到事件線程中
- 當事件滿足觸發(fā)條件時钳幅,將事件放入到JS引擎所在的執(zhí)行隊列尾部物蝙,等待JS引擎的處理
定時觸發(fā)器線程
- setInterval與setTimeout所在的線程
- 定時任務(wù)并不是由JS引擎計時的,是由定時觸發(fā)線程來計時的
- 計時完畢后敢艰,通知事件觸發(fā)線程
異步http請求線程
- 在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求
- 檢測到狀態(tài)變更時诬乞,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件钠导,將這個回調(diào)再放入事件隊列中震嫉,再由JavaScript引擎執(zhí)行。
Browser進程和Renderer進程的通信過程
- Browser進程收到用戶請求牡属,首先需要獲取頁面內(nèi)容(譬如通過網(wǎng)絡(luò)下載資源)票堵,隨后將該任務(wù)通過RendererHost接口傳遞給Render進程
- Renderer進程的Renderer接口收到消息,簡單解釋后逮栅,交給渲染線程悴势,然后開始渲染
- 渲染線程接收請求,加載網(wǎng)頁并渲染網(wǎng)頁措伐,這其中可能需要Browser進程獲取資源和需要GPU進程來幫助渲染
- 當然可能會有JS線程操作DOM(這樣可能會造成回流并重繪)
- 最后Render進程將結(jié)果傳遞給Browser進程
- Browser進程接收到結(jié)果并將結(jié)果繪制出來
javascript 是單線程的
首先是歷史原因特纤,在創(chuàng)建 javascript 這門語言時,多進程多線程的架構(gòu)并不流行侥加,硬件支持并不好捧存。
其次是因為多線程的復(fù)雜性,多線程操作需要加鎖担败,編碼的復(fù)雜性會增高昔穴。
如果同時操作 DOM ,在多線程不加鎖的情況下氢架,最終會導(dǎo)致 DOM 渲染的結(jié)果不可預(yù)期傻咖。
GUI 渲染線程與 JS 引擎線程互斥
由于 JS 是可以操作 DOM 的,如果同時修改元素屬性并同時渲染界面(即 JS線程
和UI線程
同時運行)岖研, 那么渲染線程前后獲得的元素就可能不一致了卿操。
因此警检,為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)定 GUI渲染線程
和JS引擎線程
為互斥關(guān)系害淤, 當JS引擎線程
執(zhí)行時GUI渲染線程
會被掛起扇雕,GUI更新則會被保存在一個隊列中等待JS引擎線程
空閑時立即被執(zhí)行。
WebWorker窥摄,JS的多線程镶奉?
前文中有提到JS引擎是單線程的,而且JS執(zhí)行時間過長會阻塞頁面崭放,那么JS就真的對cpu密集型計算無能為力么哨苛?
所以,后來HTML5中支持了Web Worker
币砂。
MDN的官方解釋是:
Web Worker為Web內(nèi)容在后臺線程中運行腳本提供了一種簡單的方法建峭。線程可以執(zhí)行任務(wù)而不干擾用戶界面
一個worker是使用一個構(gòu)造函數(shù)創(chuàng)建的一個對象(e.g. Worker()) 運行一個命名的JavaScript文件
這個文件包含將在工作線程中運行的代碼; workers 運行在另一個全局上下文中,不同于當前的window
因此,使用 window快捷方式獲取當前全局的范圍 (而不是self) 在一個 Worker 內(nèi)將返回錯誤
復(fù)制代碼
這樣理解下:
- 創(chuàng)建Worker時决摧,JS引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的亿蒸,完全受主線程控制,而且不能操作DOM)
- JS引擎線程與worker線程間通過特定的方式通信(postMessage API掌桩,需要通過序列化對象來與線程交互特定的數(shù)據(jù))
所以边锁,如果有非常耗時的工作,請單獨開一個Worker線程波岛,這樣里面不管如何翻天覆地都不會影響JS引擎主線程茅坛, 只待計算出結(jié)果后,將結(jié)果通信給主線程即可盆色,perfect!
WebWorker與SharedWorker
既然都到了這里灰蛙,就再提一下SharedWorker
(避免后續(xù)將這兩個概念搞混)
- WebWorker只屬于某個頁面,不會和其他頁面的Render進程(瀏覽器內(nèi)核進程)共享
- 所以Chrome在Render進程中(每一個Tab頁就是一個render進程)創(chuàng)建一個新的線程來運行Worker中的JavaScript程序隔躲。
- SharedWorker是瀏覽器所有頁面共享的摩梧,不能采用與Worker同樣的方式實現(xiàn),因為它不隸屬于某個Render進程宣旱,可以為多個Render進程共享使用
- 所以Chrome瀏覽器為SharedWorker單獨創(chuàng)建一個進程來運行JavaScript程序仅父,在瀏覽器中每個相同的JavaScript只存在一個SharedWorker進程,不管它被創(chuàng)建多少次浑吟。
看到這里笙纤,應(yīng)該就很容易明白了,本質(zhì)上就是進程和線程的區(qū)別组力。SharedWorker由獨立的進程管理省容,WebWorker只是屬于render進程下的一個線程
瀏覽器渲染流程
本來是直接計劃開始談JS運行機制的,但想了想燎字,既然上述都一直在談瀏覽器腥椒,直接跳到JS可能再突兀阿宅,因此,中間再補充下瀏覽器的渲染流程(簡單版本)
為了簡化理解笼蛛,前期工作直接省略成:(要展開的或完全可以寫另一篇超長文)
- 瀏覽器輸入url洒放,瀏覽器主進程接管,開一個下載線程滨砍,
然后進行 http請求(略去DNS查詢往湿,IP尋址等等操作),然后等待響應(yīng)惋戏,獲取內(nèi)容领追,
隨后將內(nèi)容通過RendererHost接口轉(zhuǎn)交給Renderer進程
- 瀏覽器渲染流程開始
復(fù)制代碼
瀏覽器器內(nèi)核拿到內(nèi)容后,渲染大概可以劃分成以下幾個步驟:
- 解析html建立dom樹
- 解析css構(gòu)建render樹(將CSS代碼解析成樹形的數(shù)據(jù)結(jié)構(gòu)日川,然后結(jié)合DOM合并成render樹)
- 布局render樹(Layout/reflow)蔓腐,負責(zé)各元素尺寸、位置的計算
- 繪制render樹(paint)龄句,繪制頁面像素信息
- 瀏覽器會將各層的信息發(fā)送給GPU,GPU會將各層合成(composite)散罕,顯示在屏幕上分歇。
所有詳細步驟都已經(jīng)略去,渲染完畢后就是load
事件了欧漱,之后就是自己的JS邏輯處理了
既然略去了一些詳細的步驟职抡,那么就提一些可能需要注意的細節(jié)把。
這里重繪參考來源中的一張圖:(參考來源第一篇)
load事件與DOMContentLoaded事件的先后
上面提到误甚,渲染完畢后會觸發(fā)load
事件缚甩,那么你能分清楚load
事件與DOMContentLoaded
事件的先后么?
很簡單窑邦,知道它們的定義就可以了:
- 當 DOMContentLoaded 事件觸發(fā)時擅威,僅當DOM加載完成,不包括樣式表冈钦,圖片郊丛。 (譬如如果有async加載的腳本就不一定完成)
- 當 onload 事件觸發(fā)時,頁面上所有的DOM瞧筛,樣式表厉熟,腳本,圖片都已經(jīng)加載完成了较幌。 (渲染完畢了)
所以揍瑟,順序是:DOMContentLoaded -> load
css加載是否會阻塞dom樹渲染?
這里說的是頭部引入css的情況
首先乍炉,我們都知道:css是由單獨的下載線程異步下載的绢片。
然后再說下幾個現(xiàn)象:
- css加載不會阻塞DOM樹解析(異步加載時DOM照常構(gòu)建)
- 但會阻塞render樹渲染(渲染時需等css加載完畢嘁字,因為render樹需要css信息)
這可能也是瀏覽器的一種優(yōu)化機制。
因為你加載css的時候杉畜,可能會修改下面DOM節(jié)點的樣式纪蜒, 如果css加載不阻塞render樹渲染的話,那么當css加載完之后此叠, render樹可能又得重新重繪或者回流了纯续,這就造成了一些沒有必要的損耗。 所以干脆就先把DOM樹的結(jié)構(gòu)先解析完灭袁,把可以做的工作做完猬错,然后等你css加載完之后, 在根據(jù)最終的樣式來渲染render樹茸歧,這種做法性能方面確實會比較好一點倦炒。
普通圖層和復(fù)合圖層
渲染步驟中就提到了composite
概念。
可以簡單的這樣理解软瞎,瀏覽器渲染的圖層一般包含兩大類:普通圖層
以及復(fù)合圖層
首先逢唤,普通文檔流內(nèi)可以理解為一個復(fù)合圖層(這里稱為默認復(fù)合層
,里面不管添加多少元素涤浇,其實都是在同一個復(fù)合圖層中)
其次鳖藕,absolute布局(fixed也一樣),雖然可以脫離普通文檔流只锭,但它仍然屬于默認復(fù)合層
著恩。
然后,可以通過硬件加速
的方式蜻展,聲明一個新的復(fù)合圖層
喉誊,它會單獨分配資源 (當然也會脫離普通文檔流,這樣一來纵顾,不管這個復(fù)合圖層中怎么變化调缨,也不會影響默認復(fù)合層
里的回流重繪)
可以簡單理解下:GPU中厚满,各個復(fù)合圖層是單獨繪制的荠卷,所以互不影響终吼,這也是為什么某些場景硬件加速效果一級棒
可以Chrome源碼調(diào)試 -> More Tools -> Rendering -> Layer borders
中看到,黃色的就是復(fù)合圖層信息
從 Event Loop 看 JS 的運行機制
到此時音念,已經(jīng)是屬于瀏覽器頁面初次渲染完畢后的事情沪饺,JS引擎的一些運行機制分析。
javascript從誕生之日起就是一門單線程的非阻塞的腳本語言闷愤。
單線程意味著整葡,javascript代碼在執(zhí)行的任何時候,都只有一個主線程來處理所有的任務(wù)讥脐。
而非阻塞則是當代碼需要進行一項異步任務(wù)(無法立刻返回結(jié)果遭居,需要花一定時間才能返回的任務(wù)啼器,如I/O事件)的時候,主線程會掛起(pending)這個任務(wù)俱萍,然后在異步任務(wù)返回結(jié)果的時候再根據(jù)一定規(guī)則去執(zhí)行相應(yīng)的回調(diào)端壳。
單線程的必要性上文已經(jīng)分析,為了防止DOM操作于渲染操作沖突枪蘑,以保持程序執(zhí)行的一致性损谦。
單線程在保證了執(zhí)行順序的同時也限制了javascript的效率。 web worker技術(shù)號稱讓javascript成為一門多線程語言岳颇。 然而照捡,使用web worker技術(shù)開的多線程有著諸多限制,例如:所有新線程都受主線程的完全控制话侧,不能獨立執(zhí)行栗精。這意味著這些“線程” 實際上應(yīng)屬于主線程的子線程。另外瞻鹏,這些子線程并沒有執(zhí)行I/O操作的權(quán)限悲立,只能為主線程分擔(dān)一些諸如計算等任務(wù)。所以嚴格來講這些線程并沒有完整的功能乙漓,也因此這項技術(shù)并非改變了javascript語言的feizu單線程本質(zhì)级历。
非阻塞的實現(xiàn) —— event loop
注:雖然nodejs中的也存在與傳統(tǒng)瀏覽器環(huán)境下的相似的事件循環(huán)。然而兩者間卻有著諸多不同叭披,故把兩者分開,單獨解釋玩讳。
同步代碼的執(zhí)行
當javascript代碼執(zhí)行的時候會將不同的變量存于內(nèi)存中的不同位置:堆(heap)和棧(stack)中來加以區(qū)分涩蜘。其中,堆里存放著一些對象熏纯。而棧中則存放著一些基礎(chǔ)類型變量以及對象的指針同诫。 但是我們這里說的執(zhí)行棧和上面這個棧的意義卻有些不同。
我們知道樟澜,當我們調(diào)用一個方法的時候误窖,js會生成一個與這個方法對應(yīng)的執(zhí)行環(huán)境(context),又叫執(zhí)行上下文秩贰。這個執(zhí)行環(huán)境中存在著這個方法的私有作用域霹俺,上層作用域的指向,方法的參數(shù)毒费,這個作用域中定義的變量以及這個作用域的this對象丙唧。 而當一系列方法被依次調(diào)用的時候,因為js是單線程的觅玻,同一時間只能執(zhí)行一個方法想际,于是這些方法被排隊在一個單獨的地方培漏。這個地方被稱為執(zhí)行棧。
當一個腳本第一次執(zhí)行的時候胡本,js引擎會解析這段代碼牌柄,并將其中的同步代碼按照執(zhí)行順序加入執(zhí)行棧中,然后從頭開始執(zhí)行侧甫。如果當前執(zhí)行的是一個方法珊佣,那么js會向執(zhí)行棧中添加這個方法的執(zhí)行環(huán)境,然后進入這個執(zhí)行環(huán)境繼續(xù)執(zhí)行其中的代碼闺骚。當這個執(zhí)行環(huán)境中的代碼 執(zhí)行完畢并返回結(jié)果后彩扔,js會退出這個執(zhí)行環(huán)境并把這個執(zhí)行環(huán)境銷毀,回到上一個方法的執(zhí)行環(huán)境僻爽。虫碉。這個過程反復(fù)進行,直到執(zhí)行棧中的代碼全部執(zhí)行完畢胸梆。
從圖片可知敦捧,一個方法執(zhí)行會向執(zhí)行棧中加入這個方法的執(zhí)行環(huán)境,在這個執(zhí)行環(huán)境中還可以調(diào)用其他方法碰镜,甚至是自己兢卵,其結(jié)果不過是在執(zhí)行棧中再添加一個執(zhí)行環(huán)境。這個過程可以是無限進行下去的绪颖,除非發(fā)生了棧溢出秽荤,即超過了所能使用內(nèi)存的最大值。
異步代碼執(zhí)行
js引擎遇到一個異步事件后并不會一直等待其返回結(jié)果柠横,而是會將這個事件掛起窃款,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。當一個異步事件返回結(jié)果后牍氛,js會將這個事件加入與當前執(zhí)行棧不同的另一個隊列晨继,我們稱之為事件隊列。被放入事件隊列不會立刻執(zhí)行其回調(diào)搬俊,而是等待當前執(zhí)行棧中的所有任務(wù)都執(zhí)行完畢紊扬, 主線程處于閑置狀態(tài)時,主線程會去查找事件隊列是否有任務(wù)唉擂。如果有餐屎,那么主線程會從中取出排在第一位的事件,并把這個事件對應(yīng)的回調(diào)放入執(zhí)行棧中楔敌,然后執(zhí)行其中的同步代碼...啤挎,如此反復(fù),這樣就形成了一個無限的循環(huán)。這就是這個過程被稱為“事件循環(huán)(Event Loop)”的原因庆聘。
圖中的stack表示我們所說的執(zhí)行棧胜臊,web apis則是代表一些異步事件,而callback queue即事件隊列伙判。
先理解一些概念:
- JS 分為同步任務(wù)和異步任務(wù)
- 同步任務(wù)都在JS引擎線程上執(zhí)行象对,形成一個
執(zhí)行棧
- 事件觸發(fā)線程管理一個
任務(wù)隊列
, 只要異步任務(wù)有了運行結(jié)果宴抚,就在任務(wù)隊列
之中放置一個事件勒魔。 - 一旦
執(zhí)行棧
中所有同步任務(wù)執(zhí)行完畢,此時JS引擎線程空閑菇曲,系統(tǒng)會讀取任務(wù)隊列
冠绢,將可運行的異步任務(wù)回調(diào)事件添加到執(zhí)行棧
中,開始執(zhí)行
事件循環(huán)機制進一步補充
這里就直接引用一張圖片來協(xié)助理解:(參考自Philip Roberts的演講《Help, I'm stuck in an event-loop》)
主線程運行時會產(chǎn)生執(zhí)行棧常潮, 棧中的代碼調(diào)用某些api時弟胀,它們會在事件隊列中添加各種事件(當滿足觸發(fā)條件后,如ajax請求完畢)而棧中的代碼執(zhí)行完畢喊式,就會讀取事件隊列中的事件孵户,去執(zhí)行那些回調(diào),如此循環(huán)岔留。
注意夏哭,總是要等待棧中的代碼執(zhí)行完畢后才會去讀取事件隊列中的事件
為什么有時候setTimeout推入的事件不能準時執(zhí)行?因為可能在它推入到事件列表時献联,主線程還不空閑竖配,正在執(zhí)行其它代碼, 所以自然有誤差里逆。
關(guān)于定時器
上述事件循環(huán)機制的核心是:JS引擎線程和事件觸發(fā)線程
但事件上械念,里面還有一些隱藏細節(jié),譬如調(diào)用setTimeout
后运悲,是如何等待特定時間后才添加到事件隊列中的?
是JS引擎檢測的么项钮?當然不是了班眯。它是由定時器線程控制(因為JS引擎自己都忙不過來,根本無暇分身)
為什么要單獨的定時器線程烁巫?因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確署隘,因此很有必要單獨開一個線程用來計時。
什么時候會用到定時器線程亚隙?當使用setTimeout
或setInterval
時磁餐,它需要定時器線程計時,計時完成后就會將特定的事件推入事件隊列中。
setTimeout而不是setInterval
用setTimeout模擬定期計時和直接用setInterval是有區(qū)別的诊霹。
因為每次setTimeout計時到后就會去執(zhí)行羞延,然后執(zhí)行一段時間后才會繼續(xù)setTimeout,中間就多了誤差 (誤差多少與代碼執(zhí)行時間有關(guān))
而setInterval則是每次都精確的隔一段時間推入一個事件 (但是脾还,事件的實際執(zhí)行時間不一定就準確伴箩,還有可能是這個事件還沒執(zhí)行完畢,下一個事件就來了)
而且setInterval有一些比較致命的問題就是:
- 累計效應(yīng)(上面提到的)鄙漏,如果setInterval代碼在(setInterval)再次添加到隊列之前還沒有完成執(zhí)行嗤谚, 就會導(dǎo)致定時器代碼連續(xù)運行好幾次,而之間沒有間隔怔蚌。 就算正常間隔執(zhí)行巩步,多個setInterval的代碼執(zhí)行時間可能會比預(yù)期小(因為代碼執(zhí)行需要一定時間)
- 而且把瀏覽器最小化顯示等操作時桦踊,setInterval并不是不執(zhí)行程序椅野, 它會把setInterval的回調(diào)函數(shù)放在隊列中,等瀏覽器窗口再次打開時钞钙,一瞬間全部執(zhí)行時
所以鳄橘,鑒于這么多但問題,目前一般認為的最佳方案是:用setTimeout模擬setInterval芒炼,或者特殊場合直接用requestAnimationFrame
補充:JS高程中有提到瘫怜,JS引擎會對setInterval進行優(yōu)化,如果當前事件隊列中有setInterval的回調(diào)本刽,不會重復(fù)添加鲸湃。不過,仍然是有很多問題子寓。暗挑。。
宏任務(wù)斜友、微任務(wù)
當我們基本了解了什么是執(zhí)行棧炸裆,什么是事件隊列之后,我們深入了解一下事件循環(huán)中宏任務(wù)
鲜屏、微任務(wù)
推薦觀看原文烹看,作者描述的很清晰,示例也很不錯洛史,如下:
jakearchibald.com/2015/tasks-…
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 執(zhí)行結(jié)果
/*
script start
script end
promise1
promise2
setTimeout
*/
什么是宏任務(wù)
我們可以將每次執(zhí)行棧執(zhí)行的代碼當做是一個宏任務(wù)(包括每次從事件隊列中獲取一個事件回調(diào)并放到執(zhí)行棧中執(zhí)行)惯殊, 每一個宏任務(wù)會從頭到尾執(zhí)行完畢,不會執(zhí)行其他也殖。
我們前文提到過JS引擎線程
和GUI渲染線程
是互斥的關(guān)系土思,瀏覽器為了能夠使宏任務(wù)
和DOM任務(wù)
有序的進行,會在一個宏任務(wù)
執(zhí)行結(jié)果后,在下一個宏任務(wù)
執(zhí)行前己儒,GUI渲染線程
開始工作崎岂,對頁面進行渲染。
主代碼塊址愿,setTimeout该镣,setInterval等,都屬于宏任務(wù)
什么是微任務(wù)
我們已經(jīng)知道宏任務(wù)
結(jié)束后响谓,會執(zhí)行渲染损合,然后執(zhí)行下一個宏任務(wù)
, 而微任務(wù)可以理解成在當前宏任務(wù)
執(zhí)行后立即執(zhí)行的任務(wù)娘纷。
也就是說嫁审,當宏任務(wù)
執(zhí)行完,會在渲染前赖晶,將執(zhí)行期間所產(chǎn)生的所有微任務(wù)
都執(zhí)行完律适。
Promise,process.nextTick等遏插,屬于微任務(wù)
捂贿。
補充:在node環(huán)境下,process.nextTick的優(yōu)先級高于Promise胳嘲,也就是可以簡單理解為:在宏任務(wù)結(jié)束后會先執(zhí)行微任務(wù)隊列中的nextTickQueue部分厂僧,然后才會執(zhí)行微任務(wù)中的Promise部分。
再根據(jù)線程來理解下:
- macrotask中的事件都是放在一個事件隊列中的了牛,而這個隊列由事件觸發(fā)線程維護
- microtask中的所有微任務(wù)都是添加到微任務(wù)隊列(Job Queues)中颜屠,等待當前macrotask執(zhí)行完畢后執(zhí)行,而這個隊列由JS引擎線程維護 (這點由自己理解+推測得出鹰祸,因為它是在主線程下無縫執(zhí)行的)
總結(jié)下運行機制:
- 執(zhí)行一個
宏任務(wù)
(棧中沒有就從事件隊列
中獲雀摺) - 執(zhí)行過程中如果遇到
微任務(wù)
,就將它添加到微任務(wù)
的任務(wù)隊列中 - 宏任務(wù)
執(zhí)行完畢后蛙婴,立即執(zhí)行當前
微任務(wù)隊列中的所有
微任務(wù)`(依次執(zhí)行) - 當前
宏任務(wù)
執(zhí)行完畢粗井,開始檢查渲染,然后GUI線程
接管渲染 - 渲染完畢后街图,
JS線程
繼續(xù)接管背传,開始下一個宏任務(wù)
(從事件隊列中獲取)
node環(huán)境下的事件循環(huán)機制
請直接參考
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
參考資料
https://juejin.im/post/5d5b4c2df265da03dd3d73e5#heading-0
https://juejin.im/post/5a6547d0f265da3e283a1df7#heading-0
https://juejin.im/post/5d89798d6fb9a06b102769b1#heading-1
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
https://zhuanlan.zhihu.com/p/33058983
https://segmentfault.com/a/1190000008015671
其它資料
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/