瀏覽器簡(jiǎn)單理解

瀏覽器相關(guān)理解

1 概念理解

1.1 進(jìn)程和線程
1 進(jìn)程是cpu資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位)
2 線程是cpu調(diào)度的最小單位(線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,一個(gè)進(jìn)程中可以有多個(gè)線程)
  • 進(jìn)程
    > 進(jìn)程是一個(gè)工廠,工廠有它的獨(dú)立資源
    > 工廠之間相互獨(dú)立
    
  • 線程
    > 線程是工廠中的工人,多個(gè)工人協(xié)作完成任務(wù)
    > 工廠內(nèi)有一個(gè)或多個(gè)工人
    > 工人之間共享空間
    
    - 工廠的資源 -> 系統(tǒng)分配的內(nèi)存(獨(dú)立的一塊內(nèi)存)
    
    - 工廠之間的相互獨(dú)立 -> 進(jìn)程之間相互獨(dú)立
    
    - 多個(gè)工人協(xié)作完成任務(wù) -> 多個(gè)線程在進(jìn)程中協(xié)作完成任務(wù)
    
    - 工廠內(nèi)有一個(gè)或多個(gè)工人 -> 一個(gè)進(jìn)程由一個(gè)或多個(gè)線程組成
    
    - 工人之間共享空間 -> 同一進(jìn)程下的各個(gè)線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)
    

不同進(jìn)程之間也可以通信,不過(guò)代價(jià)較大
現(xiàn)在,一般通用的叫法:?jiǎn)尉€程與多線程,都是指在一個(gè)進(jìn)程內(nèi)的單和多。(所以核心還是得屬于一個(gè)進(jìn)程才行)

2 瀏覽器的進(jìn)程和線程

2.1 概念理解
  • 瀏覽器是多進(jìn)程的
  • 瀏覽器之所以能夠運(yùn)行,是因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源(cpu、內(nèi)存)
  • 每打開(kāi)一個(gè)Tab頁(yè),就相當(dāng)于創(chuàng)建了一個(gè)獨(dú)立的瀏覽器進(jìn)程。
2.1 瀏覽器的進(jìn)程

瀏覽器的進(jìn)程

  • Browser進(jìn)程:瀏覽器的主進(jìn)程(負(fù)責(zé)協(xié)調(diào)、主控),只有一個(gè)

    負(fù)責(zé)瀏覽器界面顯示,與用戶交互。如前進(jìn),后退等
    負(fù)責(zé)各個(gè)頁(yè)面的管理,創(chuàng)建和銷毀其他進(jìn)程
    將Renderer進(jìn)程得到的內(nèi)存中的Bitmap,繪制到用戶界面上
    網(wǎng)絡(luò)資源的管理,下載等

  • 第三方插件進(jìn)程:每種類型的插件對(duì)應(yīng)一個(gè)進(jìn)程,僅當(dāng)使用該插件時(shí)才創(chuàng)建
  • GPU進(jìn)程:最多一個(gè),用于3D繪制等
  • 瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核)(Renderer進(jìn)程,內(nèi)部是多線程的):默認(rèn)每個(gè)Tab頁(yè)面一個(gè)進(jìn)程,互不影響

    頁(yè)面渲染,腳本執(zhí)行,事件處理等

多進(jìn)程的優(yōu)點(diǎn)

  • 避免單個(gè)page crash影響整個(gè)瀏覽器
  • 避免第三方插件crash影響整個(gè)瀏覽器
  • 多進(jìn)程充分利用多核優(yōu)勢(shì)
  • 方便使用沙盒模型隔離插件等進(jìn)程,提高瀏覽器穩(wěn)定性

如果瀏覽器是單進(jìn)程,那么某個(gè)Tab頁(yè)崩潰了,就影響了整個(gè)瀏覽器,體驗(yàn)有多差;同理如果是單進(jìn)程,插件崩潰了也會(huì)影響整個(gè)瀏覽器;而且多進(jìn)程還有其它的諸多優(yōu)勢(shì)

3 瀏覽器的渲染進(jìn)程

對(duì)于前端來(lái)說(shuō),最重要的是就是瀏覽器的渲染進(jìn)程。頁(yè)面的渲染,JS的執(zhí)行,事件的循環(huán),都在這個(gè)進(jìn)程內(nèi)進(jìn)行。
瀏覽器的渲染進(jìn)程是多線程的

3.1 渲染進(jìn)程中的線程

渲染進(jìn)程中有多個(gè)線程,主要是

  • 1 GUI渲染線程
    • 負(fù)責(zé)渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹和RenderObject樹,布局和繪制等。
    • 當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時(shí),該線程就會(huì)執(zhí)行
    • 注意,GUI渲染線程與JS引擎線程是互斥的,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起(相當(dāng)于被凍結(jié)了),GUI更新會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎空閑時(shí)立即被執(zhí)行。
  • 2 JS引擎線程
    • 也稱為JS內(nèi)核,負(fù)責(zé)處理Javascript腳本程序。
    • JS引擎線程負(fù)責(zé)解析Javascript腳本,運(yùn)行代碼
    • JS引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來(lái),然后加以處理,一個(gè)Tab頁(yè)(renderer進(jìn)程)中無(wú)論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行JS程序
    • 同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時(shí)間過(guò)長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞。
  • 3 事件觸發(fā)線程
    • 歸屬于瀏覽器而不是JS引擎,用來(lái)控制事件循環(huán)(可以理解,JS引擎自己都忙不過(guò)來(lái),需要瀏覽器另開(kāi)線程協(xié)助)
    • 當(dāng)JS引擎執(zhí)行代碼塊如setTimeOut時(shí)(也可來(lái)自瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點(diǎn)擊、AJAX異步請(qǐng)求等),會(huì)將對(duì)應(yīng)任務(wù)添加到事件線程中
    • 當(dāng)對(duì)應(yīng)的事件符合觸發(fā)條件被觸發(fā)時(shí),該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾,等待JS引擎的處理
    • 注意,由于JS的單線程關(guān)系,所以這些待處理隊(duì)列中的事件都得排隊(duì)等待JS引擎處理(當(dāng)JS引擎空閑時(shí)才會(huì)去執(zhí)行)
  • 4 定時(shí)觸發(fā)器線程
    • setInterval與setTimeout所在線程
    • 瀏覽器定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的,(因?yàn)镴avaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確)
    • 因此通過(guò)單獨(dú)線程來(lái)計(jì)時(shí)并觸發(fā)定時(shí)(計(jì)時(shí)完畢后,添加到事件隊(duì)列中,等待JS引擎空閑后執(zhí)行)
    • W3C在HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求setTimeout中低于4ms的時(shí)間間隔算為4ms。
  • 5 異步http請(qǐng)求線程
    • 在XMLHttpRequest在連接后是通過(guò)瀏覽器新開(kāi)一個(gè)線程請(qǐng)求
    • 將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件隊(duì)列中。再由JavaScript引擎執(zhí)行。
3.2 Browser進(jìn)程和瀏覽器內(nèi)核(Renderer進(jìn)程)的通信過(guò)程

如果自己打開(kāi)任務(wù)管理器,然后打開(kāi)一個(gè)瀏覽器,就可以看到:任務(wù)管理器中出現(xiàn)了兩個(gè)進(jìn)程(一個(gè)是主控進(jìn)程,一個(gè)則是打開(kāi)Tab頁(yè)的渲染進(jìn)程)
然后在這前提下,看下整個(gè)的過(guò)程:(簡(jiǎn)化了很多)

  • Browser進(jìn)程收到用戶請(qǐng)求,首先需要獲取頁(yè)面內(nèi)容(譬如通過(guò)網(wǎng)絡(luò)下載資源),隨后將該任務(wù)通過(guò)RendererHost接口傳遞給Render進(jìn)程
  • Renderer進(jìn)程的Renderer接口收到消息,簡(jiǎn)單解釋后,交給渲染線程,然后開(kāi)始渲染
    • 渲染線程接收請(qǐng)求,加載網(wǎng)頁(yè)并渲染網(wǎng)頁(yè),這其中可能需要Browser進(jìn)程獲取資源和需要GPU進(jìn)程來(lái)幫助渲染
    • 當(dāng)然可能會(huì)有JS線程操作DOM(這樣可能會(huì)造成回流并重繪)
    • 最后Render進(jìn)程將結(jié)果傳遞給Browser進(jìn)程
  • Browser進(jìn)程接收到結(jié)果并將結(jié)果繪制出來(lái)
3.3 瀏覽器內(nèi)核中線程之間的關(guān)系
  • GUI渲染線程與JS引擎線程互斥
    由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時(shí)渲染界面(即JS線程和UI線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。
    因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置GUI渲染線程與JS引擎為互斥的關(guān)系,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起,GUI更新則會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎線程空閑時(shí)立即被執(zhí)行。
    
  • JS阻塞頁(yè)面加載
    從上述的互斥關(guān)系,可以推導(dǎo)出,JS如果執(zhí)行時(shí)間過(guò)長(zhǎng)就會(huì)阻塞頁(yè)面。
    譬如,假設(shè)JS引擎正在進(jìn)行巨量的計(jì)算,此時(shí)就算GUI有更新,也會(huì)被保存到隊(duì)列中,等待JS引擎空閑后執(zhí)行。然后,由于巨量計(jì)算,所以JS引擎很可能很久很久后才能空閑,自然會(huì)感覺(jué)到巨卡無(wú)比。要盡量避免JS執(zhí)行時(shí)間過(guò)長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞的感覺(jué)。
    
  • WebWorker,JS的多線程
    為了因?yàn)镴S執(zhí)行時(shí)間過(guò)長(zhǎng)會(huì)阻塞頁(yè)面,后來(lái)HTML5中支持了Web Worker.
    Web Worker為Web內(nèi)容在后臺(tái)線程中運(yùn)行腳本提供了一種簡(jiǎn)單的方法。線程可以執(zhí)行任務(wù)而不干擾用戶界面
    
    一個(gè)worker是使用一個(gè)構(gòu)造函數(shù)創(chuàng)建的一個(gè)對(duì)象(e.g. Worker()) 運(yùn)行一個(gè)命名的JavaScript文件 
    
    這個(gè)文件包含將在工作線程中運(yùn)行的代碼; workers 運(yùn)行在另一個(gè)全局上下文中,不同于當(dāng)前的window
    
    因此,使用 window快捷方式獲取當(dāng)前全局的范圍 (而不是self) 在一個(gè) Worker 內(nèi)將返回錯(cuò)誤
    
    • 創(chuàng)建Worker時(shí),JS引擎向?yàn)g覽器申請(qǐng)開(kāi)一個(gè)子線程(子線程是瀏覽器開(kāi)的,完全受主線程控制,而且不能操作DOM)
    • 引擎線程與worker線程間通過(guò)特定的方式通信(postMessage API,需要通過(guò)序列化對(duì)象來(lái)與線程交互特定的數(shù)據(jù))

所以,如果有非常耗時(shí)的工作,請(qǐng)單獨(dú)開(kāi)一個(gè)Worker線程,這樣里面不管如何翻天覆地都不會(huì)影響JS引擎主線程,只待計(jì)算出結(jié)果后,將結(jié)果通信給主線程即可。

  • WebWorker與SharedWorker
    • WebWorker只屬于某個(gè)頁(yè)面,不會(huì)和其他頁(yè)面的Render進(jìn)程(瀏覽器內(nèi)核進(jìn)程)共享

      所以Chrome在Render進(jìn)程中(每一個(gè)Tab頁(yè)就是一個(gè)render進(jìn)程)創(chuàng)建一個(gè)新的線程來(lái)運(yùn)行Worker中的JavaScript程序。
      - SharedWorker是瀏覽器所有頁(yè)面共享的,不能采用與Worker同樣的方式實(shí)現(xiàn),因?yàn)樗浑`屬于某個(gè)Render進(jìn)程,可以為多個(gè)Render進(jìn)程共享使用
      >所以Chrome瀏覽器為SharedWorker單獨(dú)創(chuàng)建一個(gè)進(jìn)程來(lái)運(yùn)行JavaScript程序,在瀏覽器中每個(gè)相同的JavaScript只存在一個(gè)SharedWorker進(jìn)程,不管它被創(chuàng)建多少次。

4 瀏覽器渲染流程

為了簡(jiǎn)化理解,前期工作直接省略成:

- 瀏覽器輸入url,瀏覽器主進(jìn)程接管,開(kāi)一個(gè)下載線程,
- 然后進(jìn)行 http請(qǐng)求(略去DNS查詢,IP尋址等等操作),然后等待響應(yīng),獲取內(nèi)容,隨后將內(nèi)容通過(guò)RendererHost接口轉(zhuǎn)交給Renderer進(jìn)程
- 瀏覽器渲染流程開(kāi)始

瀏覽器器內(nèi)核拿到內(nèi)容后,渲染大概可以劃分成以下幾個(gè)步驟:

  • 解析html建立dom樹
  • 解析css構(gòu)建render樹(將CSS代碼解析成樹形的數(shù)據(jù)結(jié)構(gòu),然后結(jié)合DOM合并成render樹)
  • 布局render樹(Layout/reflow),負(fù)責(zé)各元素尺寸、位置的計(jì)算
  • 繪制render樹(paint),繪制頁(yè)面像素信息
  • 瀏覽器會(huì)將各層的信息發(fā)送給GPU,GPU會(huì)將各層合成(composite),顯示在屏幕上。
4.1 load事件與DOMContentLoaded事件的先后
  • DOMContentLoaded 事件

    當(dāng) DOMContentLoaded 事件觸發(fā)時(shí),僅當(dāng)DOM加載完成,不包括樣式表,圖片。

  • onload 事件

    當(dāng) onload 事件觸發(fā)時(shí),頁(yè)面上所有的DOM,樣式表,腳本,圖片都已經(jīng)加載完成了。

所以,順序是:DOMContentLoaded -> load

4.2 css加載是否會(huì)阻塞dom樹渲染

這里說(shuō)的是頭部引入css的情況。首先,我們都知道:css是由單獨(dú)的下載線程異步下載的。
然后再說(shuō)下幾個(gè)現(xiàn)象:

  • css加載不會(huì)阻塞DOM樹解析(異步加載時(shí)DOM照常構(gòu)建)
  • 但會(huì)阻塞render樹渲染(渲染時(shí)需等css加載完畢,因?yàn)閞ender樹需要css信息)

這可能也是瀏覽器的一種優(yōu)化機(jī)制。

因?yàn)槟慵虞dcss的時(shí)候,可能會(huì)修改下面DOM節(jié)點(diǎn)的樣式,如果css加載不阻塞render樹渲染的話,那么當(dāng)css加載完之后,render樹可能又得重新重繪或者回流了,這就造成了一些沒(méi)有必要的損耗。
所以干脆就先把DOM樹的結(jié)構(gòu)先解析完,把可以做的工作做完,然后等你css加載完之后,
在根據(jù)最終的樣式來(lái)渲染render樹,這種做法性能方面確實(shí)會(huì)比較好一點(diǎn)。
4.3 普通圖層和復(fù)合圖層

渲染步驟中就提到了composite概念。可以簡(jiǎn)單的這樣理解,瀏覽器渲染的圖層一般包含兩大類:普通圖層以及復(fù)合圖層

  • 首先,普通文檔流內(nèi)可以理解為一個(gè)復(fù)合圖層(這里稱為默認(rèn)復(fù)合層,里面不管添加多少元素,其實(shí)都是在同一個(gè)復(fù)合圖層中)
  • 其次,absolute布局(fixed也一樣),雖然可以脫離普通文檔流,但它仍然屬于默認(rèn)復(fù)合層。
  • 然后,可以通過(guò)硬件加速的方式,聲明一個(gè)新的復(fù)合圖層,它會(huì)單獨(dú)分配資源
    (當(dāng)然也會(huì)脫離普通文檔流,這樣一來(lái),不管這個(gè)復(fù)合圖層中怎么變化,也不會(huì)影響默認(rèn)復(fù)合層里的回流重繪)

可以簡(jiǎn)單理解下:

GPU中,各個(gè)復(fù)合圖層是單獨(dú)繪制的,所以互不影響,這也是為什么某些場(chǎng)景硬件加速效果一級(jí)棒

如何變成復(fù)合圖層(硬件加速)
將該元素變成一個(gè)復(fù)合圖層,就是傳說(shuō)中的硬件加速技術(shù).

  • 最常用的方式:translate3d、translateZ
  • opacity屬性/過(guò)渡動(dòng)畫(需要?jiǎng)赢媹?zhí)行的過(guò)程中才會(huì)創(chuàng)建合成層,動(dòng)畫沒(méi)有開(kāi)始或結(jié)束后元素還會(huì)回到之前的狀態(tài))
  • will-chang屬性(這個(gè)比較偏僻),一般配合opacity與translate使用(而且經(jīng)測(cè)試,除了上述可以引發(fā)硬件加速的屬性外,其它屬性并不會(huì)變成復(fù)合層),作用是提前告訴瀏覽器要變化,這樣瀏覽器會(huì)開(kāi)始做一些優(yōu)化工作(這個(gè)最好用完后就釋放)
  • <video><iframe><canvas><webgl>等元素
  • 其它,譬如以前的flash插件

absolute和硬件加速的區(qū)別

  • absolute雖然可以脫離普通文檔流,但是無(wú)法脫離默認(rèn)復(fù)合層。所以,就算absolute中信息改變時(shí)不會(huì)改變普通文檔流中render樹,但是,瀏覽器最終繪制時(shí),是整個(gè)復(fù)合層繪制的,所以absolute中信息的改變,仍然會(huì)影響整個(gè)復(fù)合層的繪制。(瀏覽器會(huì)重繪它,如果復(fù)合層中內(nèi)容多,absolute帶來(lái)的繪制信息變化過(guò)大,資源消耗是非常嚴(yán)重的)
  • 而硬件加速直接就是在另一個(gè)復(fù)合層了(另起爐灶),所以它的信息改變不會(huì)影響默認(rèn)復(fù)合層
    (當(dāng)然了,內(nèi)部肯定會(huì)影響屬于自己的復(fù)合層),僅僅是引發(fā)最后的合成(輸出視圖)

復(fù)合圖層的作用
一般一個(gè)元素開(kāi)啟硬件加速后會(huì)變成復(fù)合圖層,可以獨(dú)立于普通文檔流中,改動(dòng)后可以避免整個(gè)頁(yè)面重繪,提升性能。但是盡量不要大量使用復(fù)合圖層,否則由于資源消耗過(guò)度,頁(yè)面反而會(huì)變的更卡
硬件加速的注意點(diǎn)
使用硬件加速時(shí),盡可能的使用index,防止瀏覽器默認(rèn)給后續(xù)的元素創(chuàng)建復(fù)合層渲染。
具體的原理時(shí)這樣的:

webkit CSS3中,如果這個(gè)元素添加了硬件加速,并且index層級(jí)比較低,那么在這個(gè)元素的后面其它元素(層級(jí)比這個(gè)元素高的,或者相同的,并且releative或absolute屬性相同的),會(huì)默認(rèn)變?yōu)閺?fù)合層渲染,如果處理不當(dāng)會(huì)極大的影響性能

簡(jiǎn)單點(diǎn)理解,其實(shí)可以認(rèn)為是一個(gè)隱式合成的概念:如果a是一個(gè)復(fù)合圖層,而且b在a上面,那么b也會(huì)被隱式轉(zhuǎn)為一個(gè)復(fù)合圖層,這點(diǎn)需要特別注意

5 從Event Loop談JS的運(yùn)行機(jī)制

首先要理解上文的一些概念:

  • JS引擎線程
  • 事件觸發(fā)線程
  • 定時(shí)觸發(fā)器線程
  • JS分為同步任務(wù)和異步任務(wù)
  • 同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧
  • 主線程之外,事件觸發(fā)線程管理著一個(gè)任務(wù)隊(duì)列,只要異步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)隊(duì)列之中放置一個(gè)事件。
  • 一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢(此時(shí)JS引擎空閑),系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列,將可運(yùn)行的異步任務(wù)添加到可執(zhí)行棧中,開(kāi)始執(zhí)行。
    [圖片上傳失敗...(image-56d141-1545806196451)]

為什么有時(shí)候setTimeout推入的事件不能準(zhǔn)時(shí)執(zhí)行?因?yàn)榭赡茉谒迫氲绞录斜頃r(shí),主線程還不空閑,正在執(zhí)行其它代碼,自然有誤差。

[圖片上傳失敗...(image-cc18a5-1545806196451)]
上圖描述的過(guò)程是:

  • 1 線程運(yùn)行時(shí)會(huì)產(chǎn)生執(zhí)行棧,棧中的代碼調(diào)用某些api時(shí),它們會(huì)在事件隊(duì)列中添加各種事件(當(dāng)滿足觸發(fā)條件后,如ajax請(qǐng)求完畢)
  • 2 而棧中的代碼執(zhí)行完畢,就會(huì)讀取事件隊(duì)列中的事件,去執(zhí)行那些回調(diào)
  • 如此循環(huán)。注意,總是要等待棧中的代碼執(zhí)行完畢后才會(huì)去讀取事件隊(duì)列中的事件
5.1 定時(shí)器

上述事件循環(huán)機(jī)制的核心是:JS引擎線程和事件觸發(fā)線程。
但事件上,里面還有一些隱藏細(xì)節(jié),譬如調(diào)用setTimeout后,是如何等待特定時(shí)間后才添加到事件隊(duì)列中的?

是JS引擎檢測(cè)的么?當(dāng)然不是了。它是由定時(shí)器線程控制(因?yàn)镴S引擎自己都忙不過(guò)來(lái),根本無(wú)暇分身)

為什么要單獨(dú)的定時(shí)器線程?
因?yàn)镴avaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確,因此很有必要單獨(dú)開(kāi)一個(gè)線程用來(lái)計(jì)時(shí)。

setTimeout而不是setInterval
用setTimeout模擬定期計(jì)時(shí)和直接用setInterval是有區(qū)別的。

  • 因?yàn)槊看蝧etTimeout計(jì)時(shí)到后就會(huì)去執(zhí)行,然后執(zhí)行一段時(shí)間后才會(huì)繼續(xù)setTimeout,中間就多了誤差(誤差多少與代碼執(zhí)行時(shí)間有關(guān))
  • 而setInterval則是每次都精確的隔一段時(shí)間推入一個(gè)事件(但是,事件的實(shí)際執(zhí)行時(shí)間不一定就準(zhǔn)確,還有可能是這個(gè)事件還沒(méi)執(zhí)行完畢,下一個(gè)事件就來(lái)了)
    • 而且setInterval有一些比較致命的問(wèn)題就是:
      • 累計(jì)效應(yīng)(上面提到的),如果setInterval代碼在(setInterval)再次添加到隊(duì)列之前還沒(méi)有完成執(zhí)行,就會(huì)導(dǎo)致定時(shí)器代碼連續(xù)運(yùn)行好幾次,而之間沒(méi)有間隔。
      • 而且把瀏覽器最小化顯示等操作時(shí),setInterval并不是不執(zhí)行程序,它會(huì)把setInterval的回調(diào)函數(shù)放在隊(duì)列中,等瀏覽器窗口再次打開(kāi)時(shí),一瞬間全部執(zhí)行時(shí)
    • 就算正常間隔執(zhí)行,多個(gè)setInterval的代碼執(zhí)行時(shí)間可能會(huì)比預(yù)期小(因?yàn)榇a執(zhí)行需要一定時(shí)間)
5.2 macrotask與microtask

JS中分為兩種任務(wù)類型:macrotask和microtask,在ECMAScript中,microtask稱為jobs,macrotask可稱為task。

  • macrotask(又稱之為宏任務(wù)),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
    • 每一個(gè)task會(huì)從頭到尾將這個(gè)任務(wù)執(zhí)行完畢,不會(huì)執(zhí)行其它
    • 瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行,會(huì)在一個(gè)task執(zhí)行結(jié)束后,在下一個(gè) task 執(zhí)行開(kāi)始前,對(duì)頁(yè)面進(jìn)行重新渲染

    task->渲染頁(yè)面->task...

  • microtask(又稱為微任務(wù)),可以理解是在當(dāng)前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
    • 也就是說(shuō),在當(dāng)前task任務(wù)后,下一個(gè)task之前,在渲染之前。
    • 所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會(huì)更快,因?yàn)闊o(wú)需等渲染。
    • 也就是說(shuō),在某一個(gè)macrotask執(zhí)行完后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)

    執(zhí)行順序是:task->jobs->渲染->task->jobs->渲染....

什么樣的場(chǎng)景會(huì)形成macrotask和microtask呢?

  • macrotask:主代碼塊,setTimeout,setInterval等(可以看到,事件隊(duì)列中的每一個(gè)事件都是一個(gè)macrotask)
  • microtask:Promise,process.nextTick,MutationObserver等

補(bǔ)充:

在node環(huán)境下,process.nextTick的優(yōu)先級(jí)高于Promise__,也就是可以簡(jiǎn)單理解為:在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue部分,然后才會(huì)執(zhí)行微任務(wù)中的Promise部分。

再根據(jù)線程來(lái)理解下:

  • macrotask中的事件都是放在一個(gè)事件隊(duì)列中的,而這個(gè)隊(duì)列由事件觸發(fā)線程維護(hù)
  • microtask中的所有微任務(wù)都是添加到微任務(wù)隊(duì)列(Job Queues)中,等待當(dāng)前macrotask執(zhí)行完畢后執(zhí)行,而這個(gè)隊(duì)列由JS引擎線程維護(hù)

所以,總結(jié)下運(yùn)行機(jī)制:

  • 執(zhí)行一個(gè)宏任務(wù)(棧中沒(méi)有就從事件隊(duì)列中獲取)
  • 執(zhí)行過(guò)程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊(duì)列中
  • 宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
  • 當(dāng)前宏任務(wù)執(zhí)行完畢,開(kāi)始檢查渲染,然后GUI線程接管渲染
  • 渲染完畢后,JS線程繼續(xù)接管,開(kāi)始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲取)

[圖片上傳失敗...(image-97ebb8-1545806196452)]

注意下Promise的polyfill與官方版本的區(qū)別:

  • 官方版本中,是標(biāo)準(zhǔn)的microtask形式
  • polyfill,一般都是通過(guò)setTimeout模擬的,所以是macrotask形式

使用MutationObserver實(shí)現(xiàn)microtask

  • MutationObserver可以用來(lái)實(shí)現(xiàn)microtask,(它屬于microtask,優(yōu)先級(jí)小于Promise,
    一般是Promise不支持時(shí)才會(huì)這樣做)
  • 它是HTML5中的新特性,作用是:監(jiān)聽(tīng)一個(gè)DOM變動(dòng),當(dāng)DOM對(duì)象樹發(fā)生任何變動(dòng)時(shí),Mutation Observer會(huì)得到通知
  • 以前的Vue源碼中就是利用它來(lái)模擬nextTick的,具體原理是,創(chuàng)建一個(gè)TextNode并監(jiān)聽(tīng)內(nèi)容變化,然后要nextTick的時(shí)候去改一下這個(gè)節(jié)點(diǎn)的文本內(nèi)容.(不過(guò)2.5版本在$nextTick方法中移除了這個(gè)實(shí)現(xiàn))
    var counter = 1
    var observer = new MutationObserver(nextTickHandler)
    var textNode = document.createTextNode(String(counter))
    
    observer.observe(textNode, {
        characterData: true
    })
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容