摘要:瀏覽器渲染進(jìn)程瀏覽器內(nèi)核進(jìn)程,內(nèi)部是多線程的默認(rèn)每個(gè)頁(yè)面一個(gè)進(jìn)程,互不影響。事件觸發(fā)線程歸屬于瀏覽器而不是引擎,用來控制事件循環(huán)可以理解成引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助。
線程和進(jìn)程
進(jìn)程和線程的概念可以這樣理解:
進(jìn)程是一個(gè)工廠,工廠有它的獨(dú)立資源--工廠之間相互獨(dú)立--線程是工廠中的工人,多個(gè)工人協(xié)作完成任務(wù)--工廠內(nèi)有一個(gè)或多個(gè)工人--工人之間共享空間
工廠有多個(gè)工人,就相當(dāng)于一個(gè)進(jìn)程可以有多個(gè)線程,而且線程共享進(jìn)程的空間。
進(jìn)程是cpu資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位,系統(tǒng)會(huì)給它分配內(nèi)存)
線程是cpu調(diào)試的最小單位(線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,一個(gè)進(jìn)程中可以有多個(gè)線程。核心還是屬于一個(gè)進(jìn)程。)
瀏覽器是多進(jìn)程的,每打開一個(gè)tab頁(yè),就相當(dāng)于創(chuàng)建了一個(gè)獨(dú)立的瀏覽器進(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)程
將Rendered進(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)核)(Render進(jìn)程,內(nèi)部是多線程的):默認(rèn)每個(gè)Tab頁(yè)面一個(gè)進(jìn)程,互不影響。主要作用為:
頁(yè)面渲染,腳本執(zhí)行,事件處理等
在瀏覽器中打開一個(gè)網(wǎng)頁(yè)相當(dāng)于新起了一個(gè)進(jìn)程(進(jìn)程內(nèi)有自己的多線程)
瀏覽器多進(jìn)程的優(yōu)勢(shì)避免單個(gè)page crash影響整個(gè)瀏覽器
避免第三方插件crash影響整個(gè)瀏覽器
多進(jìn)程充分利用多核優(yōu)勢(shì)
方便使用沙盒模型隔離插件等進(jìn)程,提高瀏覽器穩(wěn)定性
簡(jiǎn)單理解就是:如果瀏覽器是單進(jìn)程的,某個(gè)Tab頁(yè)崩潰了,就影響了整個(gè)瀏覽器,體驗(yàn)就會(huì)很差。同理如果是單進(jìn)程的,插件崩潰了也會(huì)影響整個(gè)瀏覽器;
當(dāng)然,內(nèi)存等資源消耗也會(huì)更大,像空間換時(shí)間一樣。
對(duì)于普通的前端操作來說,最重要的渲染進(jìn)程:頁(yè)面的渲染,js的執(zhí)行,事件的循環(huán)等都在這個(gè)進(jìn)程內(nèi)執(zhí)行;
瀏覽器是多進(jìn)程的,瀏覽器的渲染進(jìn)程是多線程的;
GUI渲染線程負(fù)責(zé)渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹和RenderObject樹,布局和繪制等。
當(dāng)界面需要重繪或由于某種操作引發(fā)回流時(shí),該線程就會(huì)執(zhí)行。
注意,GUI渲染線程與JS引擎線程是互斥的,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起(相當(dāng)于凍結(jié)了),GUI更新會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎空閑時(shí)立即被執(zhí)行。
JS引擎線程也稱為JS內(nèi)核,負(fù)責(zé)處理JavaScript腳本程序。(例如V8引擎)。
JS引擎線程負(fù)責(zé)解析JavaScript腳本,運(yùn)行代碼。
JS引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來,然后加以處理,一個(gè)Tab頁(yè)(render進(jìn)程)中無論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行JS程序。
同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時(shí)間過長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞。
事件觸發(fā)線程歸屬于瀏覽器而不是JS引擎,用來控制事件循環(huán)(可以理解成JS引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助)。
當(dāng)JS引擎執(zhí)行代碼塊如setTimeout時(shí)(也可來自瀏覽器內(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í)行)。
定時(shí)觸發(fā)器線程傳說中的setTimeout和setInterval所在的線程
瀏覽器定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的,(因?yàn)?b>JavaScript引擎是單線程的,如果處于阻塞線程狀態(tài)就會(huì)影響計(jì)時(shí)的準(zhǔn)確)
因此通過多帶帶線程來計(jì)時(shí)并觸發(fā)定時(shí)(計(jì)時(shí)完畢后,添加到事件隊(duì)列中,等待JS引擎空閑后執(zhí)行)
注意,W3C在HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求setTimeout中低于4ms的時(shí)間間隔算為4ms。
異步http請(qǐng)求線程在XMLHttpRequest在連接后是通過瀏覽器新型一個(gè)線程請(qǐng)求
將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件隊(duì)列中,再由JavaScript引擎執(zhí)行
總結(jié)下來,渲染進(jìn)程如下:
Browser主進(jìn)程和瀏覽器內(nèi)核(渲染進(jìn)程)的通信過程打開一個(gè)瀏覽器,可以看到:任務(wù)管理器出現(xiàn)了2個(gè)進(jìn)程(一個(gè)主進(jìn)程,一個(gè)是打開Tab頁(yè)的渲染進(jìn)程);
Browser主進(jìn)程收到用戶請(qǐng)求,首先需要獲取頁(yè)面內(nèi)容(如通過網(wǎng)絡(luò)下載資源),隨后將該任務(wù)通過RendererHost接口傳遞給Render渲染進(jìn)程
Render渲染進(jìn)程的Renderer接口收到消息,簡(jiǎn)單解釋后,交給渲染線程GUI,然后開始渲染
GUI渲染線程接收請(qǐng)求,加載網(wǎng)頁(yè)并渲染網(wǎng)頁(yè),這其中可能需要Browser主進(jìn)程獲取資源和需要GPU進(jìn)程來幫助渲染
當(dāng)然可能會(huì)有JS線程操作DOM(這可能會(huì)造成回流并重繪)
最后Render渲染進(jìn)程將結(jié)果傳遞給Browser主進(jìn)程
Browser主進(jìn)程接收到結(jié)果并將結(jié)果繪制出來
瀏覽器內(nèi)核(渲染進(jìn)程)中線程之間的關(guān)系GUI渲染線程與JS引擎線程互斥
由于JavaScript是可操作DOM的,如果在修改這些元素屬性同時(shí)渲染界面(即JS線程和GUI線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。
因此,為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器就設(shè)置了互斥的關(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í)間過長(zhǎng)就會(huì)阻塞頁(yè)面。
譬如,假設(shè)JS引擎正在進(jìn)行巨量的計(jì)算,此時(shí)就算GUI有更新,也會(huì)被保存在隊(duì)列中,要等到JS引擎空閑后執(zhí)行。然后由于巨量計(jì)算,所以JS引擎可能很久很久才能空閑,肯定就會(huì)感覺很卡。
所以,要盡量避免JS執(zhí)行時(shí)間過長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞的感覺。
css加載是否會(huì)阻塞dom樹渲染
這里說的是頭部引入css的情況
首先,我們都知道:css是由多帶帶的下載線程異步下載的。
然后還有幾個(gè)現(xiàn)象:
css加載不會(huì)阻塞DOM樹解析(異步加載時(shí)dom照常構(gòu)建)
但會(huì)阻塞render樹渲染(渲染時(shí)需要等css加載完畢,因?yàn)?b>render樹需要css信息)
這可能也是瀏覽器的一種優(yōu)化機(jī)制
因?yàn)槟慵虞dcss的時(shí)候,可能會(huì)修改下面DOM節(jié)點(diǎn)的樣式,如果css加載不阻塞render樹渲染的話,那么當(dāng)css加載完之后,render樹可能又得重新重繪或者回流了,這就造成了一些沒有必要的損耗
所以干脆把DOM樹的結(jié)構(gòu)先解析完,把可以做的工作做完,然后等css加載完之后,在根據(jù)最終的樣式來渲染render樹,這種做法確實(shí)對(duì)性能好一點(diǎn)。
WebWorker,JS的多線程?
前文中有提到JS引擎是單線程的,而且JS執(zhí)行時(shí)間過長(zhǎng)會(huì)阻塞頁(yè)面,那么JS就真的對(duì)cpu密集型計(jì)算無能為力么?
所以,后來HTML5中支持了WebWorker。
這樣理解下:
創(chuàng)建Worker時(shí),JS引擎向?yàn)g覽器申請(qǐng)開一個(gè)子線程(子線程是瀏覽器開的,完全受主線程控制,而且不能操作DOM)
JS引擎線程與worker線程間通過特定的方式通信(postMessage API,需要通過序列化對(duì)象來與線程交互特定的數(shù)據(jù))
所以,如果有非常耗時(shí)的工作,請(qǐng)多帶帶開一個(gè)Worker線程,這樣里面不管如何翻天覆地都不會(huì)影響JS引擎主線程,只待計(jì)算出結(jié)果后,將結(jié)果通信給主線程即可,perfect!
而且注意下,JS引擎是單線程的,這一點(diǎn)的本質(zhì)仍然未改變,Worker可以理解是瀏覽器給JS引擎開的外掛,專門用來解決那些大量計(jì)算問題。
WebWorker與SharedWorker
既然都到了這里,就再提一下SharedWorker(避免后續(xù)將這兩個(gè)概念搞混)
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è)新的線程來運(yùn)行Worker中的JavaScript程序。
SharedWorker是瀏覽器所有頁(yè)面共享的,不能采用與Worker同樣的方式實(shí)現(xiàn),因?yàn)樗浑`屬于某個(gè)Render進(jìn)程,可以為多個(gè)Render進(jìn)程共享使用
所以Chrome瀏覽器為SharedWorker多帶帶創(chuàng)建一個(gè)進(jìn)程來運(yùn)行JavaScript程序,在瀏覽器中每個(gè)相同的JavaScript只存在一個(gè)SharedWorker進(jìn)程,不管它被創(chuàng)建多少次。
看到這里,應(yīng)該就很容易明白了,本質(zhì)上就是進(jìn)程和線程的區(qū)別。SharedWorker由獨(dú)立的進(jìn)程管理,WebWorker只是屬于render進(jìn)程下的一個(gè)線程
總結(jié)瀏覽器渲染流程瀏覽器輸入url,瀏覽器主進(jìn)程接管,開一個(gè)下載線程,然后進(jìn)行http請(qǐng)求(略去DNS查詢,IP尋址等等操作),然后等待響應(yīng),獲取內(nèi)容,隨后將內(nèi)容通過RendererHost接口轉(zhuǎn)交給Render進(jìn)程--瀏覽器渲染流程開始
瀏覽器內(nèi)核拿到內(nèi)容后,渲染大概可以劃分為:
解析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),顯示在屏幕上
渲染完畢后就是load事件了,之后就是自己的JS邏輯處理了,略去了詳細(xì)步驟。
load事件與DOMContentLoaded事件的先后
上面提到,渲染完畢后會(huì)觸發(fā)load事件,那么你能分清楚load事件與DOMContentLoaded事件的先后么?
很簡(jiǎn)單,知道它們的定義就可以了:
當(dāng) DOMContentLoaded 事件觸發(fā)時(shí),僅當(dāng)DOM加載完成,不包括樣式表,圖片。
(譬如如果有async加載的腳本就不一定完成)
當(dāng) onload 事件觸發(fā)時(shí),頁(yè)面上所有的DOM,樣式表,腳本,圖片都已經(jīng)加載完成了。(渲染完畢了)
所以,順序是:DOMContentLoaded -> load
普通圖層和復(fù)合圖層渲染步驟就提到了composite概念;瀏覽器渲染的圖層一般包含兩大類:普通圖層以及復(fù)合圖層。
普通文檔流內(nèi)可以理解為一個(gè)復(fù)合圖層(這里默認(rèn)復(fù)合層,里面不管添加多少元素,其實(shí)都是在同個(gè)復(fù)合圖層中)
absolute布局(fixed也一樣),雖然可以脫離文檔流,但它仍然屬于默認(rèn)復(fù)合層
可以通過硬件加速的方式,聲明一個(gè)新的復(fù)合圖層,它會(huì)多帶帶分配資源(當(dāng)然也會(huì)脫離普通文檔流,這樣一來,不管這個(gè)復(fù)合圖層中怎么變化,也不會(huì)影響默認(rèn)復(fù)合層里的回流重繪)
可以簡(jiǎn)單理解下:GPU中,各個(gè)復(fù)合圖層是多帶帶繪制的,所以互不影響,這也是為什么某些場(chǎng)景硬件加速效果一級(jí)棒
如何變成復(fù)合圖層(硬件加速)
將元素變成一個(gè)復(fù)合圖層,就是傳說中的硬件加速技術(shù)
最常用的方式:translate3d,translatez
opacity屬性/過渡動(dòng)畫(需要?jiǎng)赢媹?zhí)行的過程中才會(huì)創(chuàng)建合成層,動(dòng)畫沒有開始或結(jié)束后元素還會(huì)回到之前的狀態(tài))
will-chang屬性(這個(gè)比較偏僻),一般配合opacity與translate使用(而且經(jīng)測(cè)試,除了上述可以引發(fā)硬件加速的屬性外,其它屬性并不會(huì)變成復(fù)合層),作用是提前告訴瀏覽器要變化,這樣瀏覽器會(huì)開始做一些優(yōu)化工作(這個(gè)最好用完后就釋放)
等元素
其它,譬如以前的flash插件
absolute和硬件加速的區(qū)別
可以看到,absolute雖然可以脫離普通文檔流,但是無法脫離默認(rèn)復(fù)合層。
所以,就算absolute中信息改變時(shí)不會(huì)改變普通文檔流中render樹,但是,瀏覽器最終繪制時(shí),是整個(gè)復(fù)合層繪制的,所以absolute中信息的改變,仍然會(huì)影響整個(gè)復(fù)合層的繪制。(瀏覽器會(huì)重繪它,如果復(fù)合層中內(nèi)容多,absolute帶來的繪制信息變化過大,資源消耗是非常嚴(yán)重的)
而硬件加速直接就是在另一個(gè)復(fù)合層了(另起爐灶),所以它的信息改變不會(huì)影響默認(rèn)復(fù)合層(當(dāng)然了,內(nèi)部肯定會(huì)影響屬于自己的復(fù)合層),僅僅是引發(fā)最后的合成(輸出視圖)
復(fù)合圖層的作用
一般一個(gè)元素開啟硬件加速后會(huì)變成復(fù)合圖層,可以獨(dú)立于普通文檔流中,改動(dòng)后可以避免整個(gè)頁(yè)面重繪,提升性能。
但是盡量不要大量使用復(fù)合圖層,否則由于資源消耗過度,頁(yè)面反而會(huì)變的更卡。
硬件加速時(shí)請(qǐng)使用index
使用硬件加速時(shí),盡可能的使用index,防止瀏覽器默認(rèn)給后續(xù)的元素創(chuàng)建復(fù)合層渲染
具體的原理是:
webkit CSS3中,如果這個(gè)元素添加了硬件加速,并且index層級(jí)比較低,那么在這個(gè)元素的后面其它元素(層級(jí)比這個(gè)元素高的,或者相同的,并且relective或absolute屬性相同的),會(huì)默認(rèn)變?yōu)閺?fù)合層渲染,如果處理不當(dāng)會(huì)極大的影響性能
簡(jiǎn)單點(diǎn)理解,可以認(rèn)為是一個(gè)隱式合成的概念:如果a是一個(gè)復(fù)合層,而且b在a上面,那么b也會(huì)被隱式轉(zhuǎn)為一個(gè)復(fù)合圖層,這點(diǎn)需要特別注意
從Event Loop談JS的運(yùn)行機(jī)制到此時(shí),已經(jīng)是屬于瀏覽器頁(yè)面初次渲染完畢后的事情,JS引擎的一些運(yùn)行機(jī)制分析。主要是結(jié)合Event Loop來談JS代碼是如何執(zhí)行的。
我們已經(jīng)知道了JS引擎是單線程的,知道了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í)行棧,開始執(zhí)行。
看到這里,應(yīng)該就可以理解了:為什么有時(shí)候setTimeOut推入的事件不能準(zhǔn)時(shí)執(zhí)行?因?yàn)榭赡茉谒迫氲绞录斜頃r(shí),主線程還不空閑,正在執(zhí)行其它代碼,所以就必須等待,自然有誤差。
主線程在運(yùn)行時(shí)會(huì)產(chǎn)生執(zhí)行棧,棧中的代碼調(diào)用某些api時(shí),它們會(huì)在事件隊(duì)列中添加各種事件(當(dāng)滿足觸發(fā)條件后,如ajax請(qǐng)求完畢)。而當(dāng)棧中的代碼執(zhí)行完畢,就會(huì)去讀取事件隊(duì)列中的事件,去執(zhí)行那些回調(diào),如此循環(huán)。
定時(shí)器上面事件循環(huán)機(jī)制的核心是:JS引擎線程和事件觸發(fā)線程
調(diào)用setTimeout后,是由定時(shí)器線程控制等到特定時(shí)間后添加到事件隊(duì)列的,因?yàn)?b>JS引擎是單線程的,如果處于阻塞線程狀態(tài)就會(huì)影響計(jì)時(shí)準(zhǔn)確,因此很有必要另開一個(gè)線程用來計(jì)時(shí)。
當(dāng)使用setTimout或setInterval時(shí),需要定時(shí)器線程計(jì)時(shí),計(jì)時(shí)完成后就會(huì)將特定的事件推入事件隊(duì)列中。
如:
setTimeout(()=>console.log("hello!),1000) //等1000毫秒計(jì)時(shí)完畢后(由定時(shí)器線程計(jì)時(shí)),將回調(diào)函數(shù)推入事件隊(duì)列中,等待主線程執(zhí)行 setTimeout(()=>{ console.log("hello") },0) console.log("begin")
這段代碼的效果是最快的時(shí)間內(nèi)將回調(diào)函數(shù)推入事件隊(duì)列中,等待主線程執(zhí)行。
注意:
執(zhí)行結(jié)果是:先begin,后hello
雖然代碼的本意是0毫秒就推入事件隊(duì)列,但是W3C在HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求setTimeout中低于4ms的時(shí)間間隔算為4ms
就算不等待4ms,就算假設(shè)0毫秒就推入事件隊(duì)列,也會(huì)先執(zhí)行begin(因?yàn)橹荒芸蓤?zhí)行棧內(nèi)空了后才會(huì)主動(dòng)讀取事件隊(duì)列)
setInterval
用setTimeout模擬定期計(jì)時(shí)和直接用setInterval是有區(qū)別的:
每次setTimeout計(jì)時(shí)到后就會(huì)去執(zhí)行,然后執(zhí)行一段時(shí)間后才會(huì)繼續(xù)setTimeout,中間就多了誤差
而setInterval則是每次都精確的隔一段時(shí)間推入一個(gè)事件(但是,事件的實(shí)際執(zhí)行時(shí)間不一定就準(zhǔn)確,還有可能是這個(gè)事件還沒執(zhí)行完畢,下一個(gè)事件就來了)
而且setInterval有一些比較致命的問題:
累積效應(yīng),如果setInterval代碼在setInterval再次添加到隊(duì)列之前還沒有完成執(zhí)行,就會(huì)導(dǎo)致定時(shí)器代碼連續(xù)運(yùn)行好幾次,而之間沒有間隔,就算正常間隔執(zhí)行,多個(gè)setInterval的代碼執(zhí)行時(shí)間可能會(huì)比預(yù)期小(因?yàn)榇a執(zhí)行需要一定時(shí)間)
比如你ios的webview,或者safari等瀏覽器中都有一人特點(diǎn),在滾動(dòng)的時(shí)候是不執(zhí)行JS的,如果使用了setInterval,會(huì)發(fā)現(xiàn)在滾動(dòng)結(jié)束后會(huì)執(zhí)行多次由于滾動(dòng)不執(zhí)行JS積攢回調(diào),如果回調(diào)執(zhí)行時(shí)間過長(zhǎng),就會(huì)非常容易造成卡頓問題和一些不可知的錯(cuò)誤(setInterval自帶的優(yōu)化,如果當(dāng)前事件隊(duì)列中有setInterval的回調(diào),不會(huì)重復(fù)添加回調(diào))
而且把瀏覽器最小化顯示等操作時(shí),setInterval并不是不執(zhí)行程序,它會(huì)把setInterval的回調(diào)函數(shù)放在隊(duì)列中,等瀏覽器窗口再次打開時(shí),一瞬間全部執(zhí)行
所以,至于這么問題,一般認(rèn)為的最佳方案是:用setTimeout模擬setInterval或者特殊場(chǎng)合直接用requestAnimationFrame
Promise時(shí)代的microtask與macrotask在es6盛行的現(xiàn)在,可以看下這題:
console.log("script start"); setTimeout(()=>{ console.log("setTimeout") },0); Promise.resolve() .then(()=>console.log("promise1")) .then(()=>console.log("promise2")) console.log("script end") //執(zhí)行結(jié)果: script start script end promise1 promise2 setTimeout
因?yàn)?b>promise有一個(gè)新的概念microtask.或者可以說JS中分為兩種任務(wù):macrotask和microtask;
理解如下:
macrotask(又叫宏任務(wù)),主代碼塊,setTimeout,setInterval等(可以看到,事件隊(duì)列中的每一個(gè)事件都是一個(gè)macrotask)
可以理解是每次執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
第一個(gè)macrotask會(huì)從頭到尾將這個(gè)任務(wù)執(zhí)行完畢,不會(huì)執(zhí)行其它
瀏覽器為了能夠使得JS內(nèi)部macrotask與DOM任務(wù)能夠有序的執(zhí)行,會(huì)在一個(gè)macrotask執(zhí)行結(jié)束后,在下一個(gè)macrotask執(zhí)行開始前,對(duì)頁(yè)面進(jìn)行重新渲染(task->渲染->task->...)
microtask(又叫微任務(wù)),Promise,process.nextTick等。
可以理解是在當(dāng)前macrotask執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
也就是說在當(dāng)前macrotask任務(wù)后,下一個(gè)macrotask之前,在渲染之前
所以它的響應(yīng)速度相比setTimeout(setTimeout是macrotask)會(huì)更快因?yàn)闊o需等待渲染
也就是說,在某一個(gè)macrotask執(zhí)行完成后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)
注意:在Node環(huán)境下,process.nextTick的優(yōu)先級(jí)高于promise.也就是:在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTick部分,然后才會(huì)執(zhí)行微任務(wù)中的promise部分。
另外,setImmediate則是規(guī)定:在下一次Event Loop(宏任務(wù))時(shí)觸發(fā)(所以它是屬于優(yōu)先級(jí)較高的宏任務(wù)),(Node.js文檔中稱,setImmediate指定的回調(diào)函數(shù),總是排在setTimeout前面),所以setImmediate如果嵌套的話,是需要經(jīng)過多個(gè)Loop才能完成的,而不會(huì)像process.nextTick一樣沒完沒了。
可以理解:
macrotask中的事件都是放在一個(gè)事件隊(duì)列中的,而這個(gè)隊(duì)列由事件觸發(fā)線程維護(hù).
microtask中的所有微任務(wù)都是添加到微任務(wù)隊(duì)列中,等待當(dāng)前macrotask執(zhí)行完后執(zhí)行,而這個(gè)隊(duì)列由JS引擎線程維護(hù)。
所以:
執(zhí)行一個(gè)宏任務(wù)(棧中沒有就從事件隊(duì)列中獲取)
執(zhí)行過程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊(duì)列中
宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染
渲染完畢后,JS線程繼續(xù)接管,開始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲取)
new Promise里的函數(shù)是直接執(zhí)行的算做主程序里,而且.then后面的才會(huì)放到微任務(wù)中。
另外,請(qǐng)注意下Promise的polyfill與官方版本的區(qū)別:
官方版本中,是標(biāo)準(zhǔn)的microtask形式
polyfill,一般都是通過setTimeout模擬的,所以是macrotask形式
請(qǐng)?zhí)貏e注意這兩點(diǎn)區(qū)別
注意,有一些瀏覽器執(zhí)行結(jié)果不一樣(因?yàn)樗鼈兛赡馨?b>microtask當(dāng)成macrotask來執(zhí)行了),但是為了簡(jiǎn)單,這里不描述一些不標(biāo)準(zhǔn)的瀏覽器下的場(chǎng)景(但記住,有些瀏覽器可能并不標(biāo)準(zhǔn))
Mutation Observer可以用來實(shí)現(xiàn)microtask(它屬于microtask,優(yōu)先級(jí)小于Promise,一般是Promise不支持時(shí)才會(huì)這樣做)
它是HTML5中的新特性,作用是:監(jiān)聽一個(gè)DOM變動(dòng),當(dāng)DOM對(duì)象樹發(fā)生任何變動(dòng)時(shí),Mutation Observer會(huì)得到通知
像以前的Vue源碼中就是利用它來模擬nextTick的,具體原理是,創(chuàng)建一個(gè)TextNode并監(jiān)聽內(nèi)容變化,然后要nextTick的時(shí)候去改一下這個(gè)節(jié)點(diǎn)的文本內(nèi)容,如下:(Vue的源碼,未修改)
var counter=1 var observer=newMutationObserver(nextTickHandler) var textNode=document.createTextNode(String(counter)) observer.observe(textNode,{characterData:true}) timerFunc=()=>{ counter=(counter+1)%2 textNode.data=String(counter) }
不過,現(xiàn)在的Vue(2.5+)的nextTick實(shí)現(xiàn)移除了Mutation Observer的方式(據(jù)說是兼容性原因),取而代之的是使用MessageChannel(當(dāng)然,默認(rèn)情況仍然是Promise,不支持才兼容的)。
MessageChannel屬于宏任務(wù),優(yōu)先級(jí)是:setImmediate->MessageChannel->setTimeout,所以Vue(2.5+)內(nèi)部的nextTick與2.4及之前的實(shí)現(xiàn)是不一樣的,需要注意下。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93748.html
摘要:模塊和將下面的渲染機(jī)制,安全機(jī)制,插件機(jī)制等等隱藏起來,提供一個(gè)接口層。進(jìn)行網(wǎng)頁(yè)的渲染進(jìn)程,可能有多個(gè)。最后進(jìn)程將結(jié)果由線程傳遞給進(jìn)程最后,進(jìn)程接收到結(jié)果并將結(jié)果繪制出來。 這是之前在簡(jiǎn)書上面的處女作,也搬過來了,以后就一直在 segmentfault 上面寫文章了,webkit技術(shù)內(nèi)幕-朱永盛是我大四買的書,很舊的一本書了,當(dāng)時(shí)只看了一點(diǎn)點(diǎn),一直沒繼續(xù)看完它,現(xiàn)在才看完,,,說來慚愧...
摘要:前端頁(yè)面渲染機(jī)制筆記瀏覽器基礎(chǔ)結(jié)構(gòu)用戶界面用戶所看到及與之交互的功能組件,如地址欄返回前進(jìn)按鈕瀏覽器引擎用戶界面和呈現(xiàn)引擎之間傳遞指令渲染引擎呈現(xiàn)引擎負(fù)責(zé)解析用戶請(qǐng)求的內(nèi)容網(wǎng)絡(luò)負(fù)責(zé)處理網(wǎng)絡(luò)相關(guān)的事物后端負(fù)責(zé)繪制提示框等瀏覽器組件,底層使用 前端頁(yè)面渲染機(jī)制-筆記 瀏覽器基礎(chǔ)結(jié)構(gòu) 1.用戶界面(user interface):用戶所看到及與之交互的功能組件,如地址欄、返回、前進(jìn)按鈕 2...
摘要:前端頁(yè)面渲染機(jī)制筆記瀏覽器基礎(chǔ)結(jié)構(gòu)用戶界面用戶所看到及與之交互的功能組件,如地址欄返回前進(jìn)按鈕瀏覽器引擎用戶界面和呈現(xiàn)引擎之間傳遞指令渲染引擎呈現(xiàn)引擎負(fù)責(zé)解析用戶請(qǐng)求的內(nèi)容網(wǎng)絡(luò)負(fù)責(zé)處理網(wǎng)絡(luò)相關(guān)的事物后端負(fù)責(zé)繪制提示框等瀏覽器組件,底層使用 前端頁(yè)面渲染機(jī)制-筆記 瀏覽器基礎(chǔ)結(jié)構(gòu) 1.用戶界面(user interface):用戶所看到及與之交互的功能組件,如地址欄、返回、前進(jìn)按鈕 2...
摘要:事件循環(huán)機(jī)制首先區(qū)分進(jìn)程和線程進(jìn)程是資源分配的最小單位系統(tǒng)會(huì)給它分配內(nèi)存不同的進(jìn)程之間是可以同學(xué)的,如管道命名管道消息隊(duì)列一個(gè)進(jìn)程里有單個(gè)或多個(gè)線程瀏覽器是多進(jìn)程的,因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源內(nèi)存打開會(huì)有一個(gè)主進(jìn)程,每打開一個(gè)頁(yè)就有一個(gè)獨(dú) JS JavaScript事件循環(huán)機(jī)制 首先區(qū)分進(jìn)程和線程 進(jìn)程是cpu資源分配的最小單位(系統(tǒng)會(huì)給它分配內(nèi)存) 不同的進(jìn)程之間是可以同學(xué)的,如...
摘要:如果看完本文后,還對(duì)進(jìn)程線程傻傻分不清,不清楚瀏覽器多進(jìn)程瀏覽器內(nèi)核多線程單線程運(yùn)行機(jī)制的區(qū)別。因此準(zhǔn)備梳理這塊知識(shí)點(diǎn),結(jié)合已有的認(rèn)知,基于網(wǎng)上的大量參考資料,從瀏覽器多進(jìn)程到單線程,將引擎的運(yùn)行機(jī)制系統(tǒng)的梳理一遍。 前言 見解有限,如有描述不當(dāng)之處,請(qǐng)幫忙及時(shí)指出,如有錯(cuò)誤,會(huì)及時(shí)修正。 ----------超長(zhǎng)文+多圖預(yù)警,需要花費(fèi)不少時(shí)間。---------- 如果看完本文后,還...
摘要:渲染機(jī)制瀏覽器渲染機(jī)制什么是及作用告訴瀏覽器文件是什么文檔類型,瀏覽器根據(jù)它來判斷用什么引擎來解析渲染文件。觸發(fā)改動(dòng)改動(dòng)例當(dāng)添加時(shí),最好一次添加,避免多次。 渲染機(jī)制 瀏覽器 1. 渲染機(jī)制 什么是 DOCTYPE 及作用 DTD 告訴瀏覽器文件是什么文檔類型,瀏覽器根據(jù)它來判斷用什么引擎來解析渲染文件。DOCTYPE 用來聲明文檔類型和 DTD 規(guī)范。 瀏覽器是怎么渲染過程show...
閱讀 2932·2023-04-26 01:49
閱讀 2066·2021-10-13 09:39
閱讀 2278·2021-10-11 11:09
閱讀 922·2019-08-30 15:53
閱讀 2815·2019-08-30 15:44
閱讀 916·2019-08-30 11:12
閱讀 2965·2019-08-29 17:17
閱讀 2370·2019-08-29 16:57