當(dāng)瀏覽器渲染一個(gè)html都發(fā)生了什么哮笆?
這個(gè)問題基本在面試的時(shí)候經(jīng)常會(huì)問到吧。經(jīng)常的回答是當(dāng)html 被加載到瀏覽器中霎俩,解析html構(gòu)建dom tree袭厂,通過link解析css,構(gòu)建render tree媳纬。把這倆個(gè)組合在一起双肤,渲染出看到的頁面施掏。(精簡的回答。)對(duì)的茅糜,但不全面七芭。本文進(jìn)行深度剖析。
一蔑赘,圖解流程
1狸驳,解析代碼。瀏覽器包含html解析器和css解析器缩赛。分別解析html和css代碼耙箍。html解析器會(huì)解析出dom,css解析器會(huì)解析出cssDom峦筒。cssDom的樣式映射都是全部計(jì)算過的究西,是個(gè)絕對(duì)值。我們?cè)诖a中寫的往往不是最終結(jié)果(元素可能繼承物喷,默認(rèn)卤材,calc等其他影響,瀏覽器不理解相對(duì)概念只會(huì)以絕對(duì)值展示)峦失。dom和cssDom都是樹型結(jié)構(gòu)扇丛。
2,合并樹尉辑,從Dom根結(jié)點(diǎn)開始遍歷每一個(gè)可見的節(jié)點(diǎn)帆精,為其找到適配的cssDom規(guī)則并應(yīng)用。將兩個(gè)樹合并成render tree 隧魄。a.某些節(jié)點(diǎn)不可見(如腳本標(biāo)簽<script>此類)b.某些節(jié)點(diǎn)被CSS隱藏了卓练,類似display:none。注意购啄,visibility:hidden此類屬于可見節(jié)點(diǎn)襟企,其仍然占據(jù)著布局空間。最終輸出的RenderTree將同時(shí)包含屏幕上的所有可見內(nèi)容及其樣式信息狮含。
3顽悼,layout,css提供了部分布局信息几迄,但瀏覽器想要的是元素相對(duì)于設(shè)備視窗內(nèi)的確切位置和大小蔚龙。僅僅靠render tree是不足以描述我們的頁面,我們?nèi)狈?jié)點(diǎn)之間確切的位置關(guān)系映胁。瀏覽器默認(rèn)通過文檔流進(jìn)行布局木羹,并通過css盒模型來描述每一個(gè)元素。所以我們可以輕易的捕獲元素在視窗內(nèi)的確切尺寸和大小屿愚,所有的相對(duì)測量值都會(huì)轉(zhuǎn)換成屏幕上的絕對(duì)像素汇跨。諸如float,flex之類的布局方式都需要重新計(jì)算务荆。布局信息會(huì)被存儲(chǔ)在一個(gè)與渲染樹相關(guān)聯(lián)的樹結(jié)構(gòu)里。layout階段結(jié)束后會(huì)輸出名為layoutTree的數(shù)據(jù)結(jié)構(gòu)穷遂。
4函匕,painting ,遍歷渲染樹蚪黑,進(jìn)行每個(gè)節(jié)點(diǎn)的繪制盅惜。
二,瀏覽器的進(jìn)程
對(duì)于進(jìn)程和線程的定義和理解請(qǐng)參考以下鏈接:
1忌穿,https://segmentfault.com/a/1190000012925872
2抒寂,http://imweb.io/topic/58e3bfa845e5c13468f567d5
以目前的Chrome為例,它采用的是多進(jìn)程架構(gòu)掠剑。不同的進(jìn)程有不同的功能屈芜。比如browser process主要負(fù)責(zé)瀏覽器界面顯示,與用戶交互朴译。包括地址欄井佑,書簽,后退和前進(jìn)按鈕等眠寿。頁面的渲染主要是在renderer process中進(jìn)行的躬翁。
1,Renderer process下的線程
a,主線程(main thread)
b盯拱,合成線程(COmpositor thread)
一般情況下盒发,諸如計(jì)算HTML 元素的 CSS 樣式,layout狡逢,paint和運(yùn)行JavaScript都運(yùn)行在主線程之中宁舰。我想你已經(jīng)注意到了,js的運(yùn)行也在主線程奢浑。這就是意味著明吩,樣式計(jì)算,layout殷费,paint和js的執(zhí)行是一種互斥關(guān)系。它們會(huì)互相阻塞低葫。如果正在計(jì)算布局详羡,那么js就無法及時(shí)的監(jiān)聽到事件。反過來嘿悬,js執(zhí)行占據(jù)了大量主線程時(shí)間实柠,你的layout和paint都會(huì)受到影響。
概覽
主線程:圖層的誕生
原有的layoutTree可以反映在二維平面上節(jié)點(diǎn)的相互關(guān)系善涨,但無法反映層次關(guān)系窒盐。
GraphicsLayer結(jié)束后還會(huì)轉(zhuǎn)換為cc layer炕横。但是樹的結(jié)構(gòu)不會(huì)再有更改∑狭#可以理解GraphicsLayer就是我們最終需要生成的圖層份殿。
chrome會(huì)將它認(rèn)為是同一層坐標(biāo)體系下的layoutObject歸為一個(gè)Layer內(nèi)。
每一個(gè)LayoutObject都直接的或間接的與一個(gè)PaintLayer有關(guān)聯(lián)嗽交。那些共享同一坐標(biāo)空間的LayoutObject往往處在同一PaintLayer中卿嘲。如果沒有觸發(fā)上述條件,會(huì)和父元素共享一個(gè)坐標(biāo)空間夫壁。
特殊的dom胁澳,canvas浸策,video,根對(duì)象。特殊的css机蔗,css位置屬性,transform纽乱,透明度忿峻,filter等。
chrome會(huì)將它認(rèn)為是一層的paintLayer歸為一個(gè)GraphicsLayer內(nèi)撩扒。
事情并沒有結(jié)束似扔,chrome覺得目前的曾數(shù)量依然太多了。所以PaintLayer依然要繼續(xù)變形搓谆,繼續(xù)壓縮層的數(shù)量炒辉。由paintLayer轉(zhuǎn)化成graphicsLayer。那么什么是GraphicsLayer呢泉手?這里拋出一個(gè)可能大家都早已聽說過得概念:合成層黔寇。GraphicsLayer就是最終合成的候選人。
主線程:PAINT
到目前為止斩萌,我們?nèi)匀贿€在主線程中缝裤。我們剛剛這一系列l(wèi)ayer的轉(zhuǎn)換,都發(fā)生在layout和paint中間颊郎。當(dāng)我們滿心歡喜的拿到合成層時(shí)憋飞,是不是覺得快結(jié)束了,終于繪制了姆吭!很遺憾榛做,這個(gè)Paint可能和你想象中的階段不大一樣。
籠統(tǒng)的將三個(gè)階段解讀為一個(gè)Painting階段是不嚴(yán)謹(jǐn)?shù)模瑢?shí)際上Paint僅僅是其中第一個(gè)階段而已检眯。傳遞給Paint的是CC Layer厘擂。所以每一層都擁有獨(dú)立的Paint。每層都會(huì)擁有一個(gè)DisplayItem List來記錄該層所有的繪制操作锰瘸。注意刽严,只是記錄操作,沒有任何繪制行為的發(fā)生获茬。Displayitem list會(huì)與合成層綁定港庄,然后合成層被當(dāng)做主線程的最終輸出交給合成線程
合成線程:Commit
commit階段,主線程會(huì)提交一份cc layer的拷貝放入合成線程恕曲。隨后主線程便會(huì)進(jìn)入下一幀的渲染流程中鹏氧。兩個(gè)線程同步進(jìn)行,互不影響佩谣。commit階段雖然會(huì)短暫阻塞主線程把还,但是時(shí)間開銷很小。
合成線程:Tiling(鋪磚茸俭,蓋瓦的意思)
一個(gè)頁面的實(shí)際長度可能遠(yuǎn)遠(yuǎn)超過視窗大小吊履。將整個(gè)頁面每次都渲染出來是不明智的,因?yàn)橛脩粑幢卣娴臅?huì)瀏覽到視窗外的部分调鬓,這樣反而消耗了資源艇炎。除了縱橫向滾動(dòng)依次排列的磁貼,在存在zoom縮放操作時(shí)腾窝,也會(huì)根據(jù)每次縮放的情況來決定渲染哪些磁貼缀踪。所以在一些性能較差的手機(jī)上,對(duì)超長列表進(jìn)行快速滾動(dòng)虹脯,有時(shí)可以發(fā)現(xiàn)底部會(huì)有短暫白色的情況驴娃。這正是tiling階段動(dòng)態(tài)渲染的原因。
合成線程:Raster
主線程中paint階段生成的命令會(huì)在該階段執(zhí)行循集,將圖像壓進(jìn)位圖之中唇敞。位圖就是像素點(diǎn)陣圖。光柵化就是將圖像畫進(jìn)位圖的過程咒彤。之前我們?cè)趐aint存儲(chǔ)的繪制指令現(xiàn)在會(huì)拿出來執(zhí)行疆柔,以磁貼為單位。光柵化結(jié)束后這些位圖會(huì)上傳至GPU內(nèi)存之中镶柱。
合成線程:Activation
合成線程內(nèi)有兩棵樹婆硬。active樹在執(zhí)行完draw階段提交gpu線程繪制后,下一幀的pendingTree便可以繼續(xù)工作了奸例,不必等待畫面完全輸出至頁面上。一幀畫面只有真正activation之后變成active Tree才算進(jìn)入渲染流程。
合成線程:draw
draw階段仍然是對(duì)指令的包裝查吊。每個(gè)tile都會(huì)被“繪制”成“繪圖塊(draw quads)”谐区。每一個(gè)繪圖塊是一個(gè)在屏幕上指定位置繪制tile的指令。這個(gè)有點(diǎn)像我們?cè)谥骶€程Paint階段做得事逻卖。每一個(gè)塊都會(huì)包含對(duì)相應(yīng)tile在gpu內(nèi)存中的引用宋列。而所有繪圖塊會(huì)被包裝在一個(gè)CompositorFrame對(duì)象中。這個(gè)對(duì)象就是最終合成線程的終點(diǎn)评也,或者說炼杖,它就是整個(gè)renderer process的最終輸出。這個(gè)對(duì)象會(huì)被提交給browser process盗迟。
合成線程:display
Browser process會(huì)整合各個(gè)process提交的compositorFrame坤邪,讓gpu輸出真正的最終畫面。除了rederer process渲染的主頁面罚缕。plugin process也會(huì)影響畫面艇纺。那些我們常用的Chrome插件就是在這個(gè)階段與我們的頁面進(jìn)行整合的。
以上就是渲染的全部過程邮弹。當(dāng)然頁面也會(huì)因其他因素而改變黔衡。
1,用戶交互(包括滾動(dòng)腌乡,縮放盟劫,點(diǎn)擊等一系列操作)
2,js對(duì)dom的更新与纽。
用戶交互
js更新dom
JS對(duì)DOM的更新是需要謹(jǐn)慎的侣签,它往往會(huì)觸發(fā)從主線程到合成線程中的所有階段!
雖然聽起來很可怕渣锦,但是只要不是短期內(nèi)反復(fù)更新硝岗,現(xiàn)在的機(jī)器性能都足以應(yīng)對(duì)。但如果是一個(gè)無線循環(huán)動(dòng)畫之類的袋毙,就需要萬分謹(jǐn)慎了型檀。