瀏覽器原理機制

瀏覽器架構(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)容后,渲染大概可以劃分成以下幾個步驟:

  1. 解析html建立dom樹
  2. 解析css構(gòu)建render樹(將CSS代碼解析成樹形的數(shù)據(jù)結(jié)構(gòu)日川,然后結(jié)合DOM合并成render樹)
  3. 布局render樹(Layout/reflow)蔓腐,負責(zé)各元素尺寸、位置的計算
  4. 繪制render樹(paint)龄句,繪制頁面像素信息
  5. 瀏覽器會將各層的信息發(fā)送給GPU,GPU會將各層合成(composite)散罕,顯示在屏幕上分歇。

所有詳細步驟都已經(jīng)略去,渲染完畢后就是load事件了欧漱,之后就是自己的JS邏輯處理了

既然略去了一些詳細的步驟职抡,那么就提一些可能需要注意的細節(jié)把。

這里重繪參考來源中的一張圖:(參考來源第一篇)

render.png

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í)行完畢胸梆。

executionstack.png

從圖片可知敦捧,一個方法執(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)”的原因庆聘。

eventloop2.jpg

圖中的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í)行
eventloop.png

事件循環(huán)機制進一步補充

這里就直接引用一張圖片來協(xié)助理解:(參考自Philip Roberts的演講《Help, I'm stuck in an event-loop》)

eventloop1.png

主線程運行時會產(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)就會影響記計時的準確署隘,因此很有必要單獨開一個線程用來計時。

什么時候會用到定時器線程亚隙?當使用setTimeoutsetInterval磁餐,它需要定時器線程計時,計時完成后就會將特定的事件推入事件隊列中。

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://vimeo.com/96425312

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末台夺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痴脾,更是在濱河造成了極大的恐慌颤介,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滚朵,居然都是意外死亡冤灾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門辕近,熙熙樓的掌柜王于貴愁眉苦臉地迎上來韵吨,“玉大人,你說我怎么就攤上這事移宅」榉郏” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵漏峰,是天一觀的道長糠悼。 經(jīng)常有香客問我,道長浅乔,這世上最難降的妖魔是什么倔喂? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮靖苇,結(jié)果婚禮上席噩,老公的妹妹穿的比我還像新娘。我一直安慰自己贤壁,他們只是感情好悼枢,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芯砸,像睡著了一般萧芙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上假丧,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天双揪,我揣著相機與錄音,去河邊找鬼包帚。 笑死渔期,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的渴邦。 我是一名探鬼主播疯趟,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谋梭!你這毒婦竟也來了信峻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤瓮床,失蹤者是張志新(化名)和其女友劉穎盹舞,沒想到半個月后产镐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡踢步,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年癣亚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片获印。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡述雾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兼丰,到底是詐尸還是另有隱情玻孟,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布地粪,位于F島的核電站取募,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蟆技。R本人自食惡果不足惜玩敏,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望质礼。 院中可真熱鬧旺聚,春花似錦、人聲如沸眶蕉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽造挽。三九已至碱璃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饭入,已是汗流浹背嵌器。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谐丢,地道東北人爽航。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像乾忱,于是被迫代替她去往敵國和親讥珍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348