小謝第67問:使用者輸入一個URL到頁面渲染完成具體發生了什麼?
一、前言
當用戶輸入一個URL到頁面渲染完成具體發生了什麼?
先回顧一下之前寫的一篇文章輸入url到頁面渲染全鏈路分析,主要分析了瀏覽器從載入ULR到伺服器返回資源的過程,如果不太瞭解可以看看這篇(又增加了更詳細的內容)
當瀏覽器發出請求,伺服器返回對應的資源後,瀏覽器又做了哪些工作將位元組流轉化成漂亮的頁面?
針對這個問題,需要對瀏覽器的工作原理進行深入的研究,才能清楚的知道瀏覽器在這個過程中,做了哪些操作,就能幫助我們在前端開發或前端效能優化的時候,針對某個環節採取不同的方法進行優化,而不是隻會背誦減少http請求,合併背景圖等。
上篇文章我們知道了,瀏覽器會通過網路程序請求網路資源,然後網路程序和渲染程序建立"管道",放入訊息佇列等待渲染執行緒進行處理。
二、渲染程序下的各個執行緒
現代瀏覽器是多程序架構,其中程序中又包含了多個執行緒,而處理html解析和頁面渲染主要就發生在渲染程序中,就是我們常說的瀏覽器核心,渲染程序主要包括了GUI渲染執行緒、js引擎執行緒、事件觸發執行緒、定時觸發執行緒、合成執行緒、IO執行緒等
1、渲染執行緒
渲染執行緒主要負責頁面的渲染工作,解析html、css、構建佈局樹、繪製圖層等操作
2、JS引擎執行緒
JS引擎執行緒是瀏覽器用來執行js的直譯器,常見的一種實現方式就是V8引擎。
JS引擎執行緒和渲染執行緒互斥,即同時只能有一個執行緒在執行。
這是因為JS可以對DOM節點進行增刪改,所以如果在渲染的過程中,JS同時修改了DOM,就會不斷的重複渲染,
所以JS引擎在設計之初就設計了兩者互斥,當JS引擎工作的時候,渲染執行緒就掛起等待,
這就導致了我們經常遇到的一個問題,當js執行時間過長會造成頁面卡頓。
3、事件觸發執行緒
我們知道JS是靠事件驅動的語言,它是單執行緒,非同步執行的。主要處理瀏覽器的各種事件,比如點選事件,移動事件,然後將事件放入任務佇列末尾等待JS引擎執行。
4、定時觸發執行緒
主要負責處理JS中的定時器,因為JS引擎是單執行緒的,可能存在阻塞的情況,所以再開一個執行緒負責可以保證定時器的準確性。
當然我們通常通過回撥函式執行定時的事件,而回調事件觸發後會被加入任務佇列末尾等待執行,所以如果JS引擎阻塞後,具體的執行時間還是會有誤差。
5、非同步請求執行緒
當有XMLHttpRequest請求時,會新開執行緒發出請求,等返回狀態變更時,會觸發回撥事件,將事件放入任務佇列等待JS引擎執行。
當渲染執行緒拿到返回的資源,會發生如下整個過程,下面我們來一一說明,具體的渲染流程圖
- DOM解析
- 位元組流詞牌解析
- 轉化Token
- DOM樹構建
- CSSOM解析
- CSS令牌
- CSS格式化
- CSSOM構建
- 佈局樹
- DOM和CSSOM計算佈局樹
- 計算DOM節點的座標,構建佈局樹
- 圖層樹
- 根據佈局樹構建圖層樹
- 繪製列表、合成
- 渲染引擎根據圖層樹,生成繪製列表,交給合成執行緒
- 合成執行緒將繪製列表生成圖塊
- 光柵化
- 進行光柵化操作,將圖塊合成點陣圖,放入光柵化執行緒池
- 生成點陣圖,優先生成視口附近的點陣圖
- 顯示
- 然後交給瀏覽器程序,通過瀏覽器元件繪製成圖片到記憶體,顯示出來
三、構建DOM樹
這裡通過瀏覽器開發者工具中的Performance
面板錄製了頁面載入過程中,瀏覽器執行的任務,可以清晰的看到有個解析HTML的任務,事件的載入和JS先解析再編譯等。
其實後面有很多工,但截圖太多沒法顯示資訊,就截圖了一部分,可以自行測試。
這一步渲染執行緒獲取資源後會獲取返回頭資訊,如果頭部資訊存在標識content_type: html
,網路程序會和渲染執行緒建立通道,就像流水線一樣,渲染引擎的HTML模組解析器,首先通過詞解析,生成對應的Token,就是標記<StartTag>
和<EndTag>
,然後壓入它維護的棧中,同時生成對應的Node節點,通過不斷的對比開始節點和結束節點,最終構建出DOM樹。
在詞解析的過程中,如果發現了style和script的引用檔案,瀏覽器另開啟執行緒進行預下載。當DOM樹構建完成,開始解析預下載的CSS。
在解析DOM的過程中,如果有JS指令碼,渲染執行緒會停止工作,等待JS引擎的執行,所以說在文件頭部引用JS會導致頁面渲染卡頓。
上面這個是打印出來的DOM結構,這個結構給我們通過JS操作DOM提供了可能。
四、構建styleSheet樣式表
這一步主要發生了:CSS解析、CSS標準化、styleSheets構建(CSSOM)
CSS主要有四個來源:
- 1、內聯樣式
- 2、style標籤嵌入
- 3、link引入
- 4、js引入
在解析DOM的過程中,不管遇到哪種樣式,渲染引擎都會在將CSS標準化完成後,加入到如下的StyleSheetList表中,這樣就為後面我們操作CSS提供了便利,在控制檯Console中可以打印出來document.styleSheets
其中CSS標準化就是將一些瀏覽器不能識別的語法標準化成可以識別的。比如
.demo {
font-size: 2em;
color: #000;
}
複製程式碼
轉化成
.demo {
font-size: 28px;
color: rgb(0, 0, 0);
}
複製程式碼
五、佈局樹,計算樣式和Node節點座標
有了DOM樹和StyleSheetss,就可以開始構建佈局樹了。具體就是遍歷DOM節點,為每個節點計算樣式,過程中隱藏的和不可見的元素是不會加入佈局樹的,這個過程包括:計算佈局樹和渲染布局樹。
我們可以在Elements這裡看到每個節點計算的樣式。
這個過程中,我們發現構建佈局樹需要DOM和CSS樣式表(這裡可以理解成CSSOM)。
如果CSS下載時間過長,導致CSSOM沒有下載或解析完成,渲染執行緒就會停下來等待CSS處理。
所以如果CSS檔案比較大或者網路差,就會導致頁面最終的渲染時間增加。
如果在載入DOM的過程中執行了JS程式碼,JS中又包含CSS,那麼渲染執行緒也會等待CSS下載,這樣也會影響渲染的時間。
六、圖層樹
構建完成了佈局樹,此時還不會進行繪製,渲染引擎會通過佈局樹構建圖層樹。
就像PS一樣,每張圖片都是由若干個圖層覆蓋,最後顯示出來一副圖片,圖層樹就是這樣,將頁面處理成為一個一個層級,每個節點都有所在的層級,如果沒有就歸屬於父節點。
如圖所示,在瀏覽器layers欄可以看到瀏覽器分層的效果
點選下面的繪製列表,拖動繪製步驟就可以重現繪製過程
常見的可以引起分層的樣式有z-index
,DIV內容大於寬度出現的裁剪或出現滾動條
,Fix
,3D渲染
等
七、合成繪製列表、光柵化操作
構建完成圖層樹,渲染引擎會將圖層轉化成繪製列表,如上圖所示,這個列表只有繪製指令。
接著渲染引擎會將繪製列表交給合成執行緒,合成執行緒會將繪製列表繪製成圖塊,然後執行光柵化操作,就是大頁面分割成256*256
或512*512
的大小,然後生成點陣圖,優先渲染頁面的可視區域,這也是瀏覽器在渲染上做的優化。
渲染引擎會會維護一個光柵化執行緒池,一旦光柵化完成,就會將繪製指令交給瀏覽器程序。如果這一步操作使用GUI加速,那麼後續操作也會在GUI程序中操作,這樣就不會影響渲染程序,提高了繪製的速度。
八、繪製
合成執行緒將繪製點陣圖的指令提交給瀏覽器程序後,瀏覽器程序會呼叫它的viz 的元件根據繪製指令將他們繪製到記憶體中,然後在頁面顯示出來。
這樣整個過程就完成了。
九、重排和重繪
重排和重繪是面試中經常問到的一個知識點,如果開發中做動畫比較多,那瞭解這方面的內容可以優化動畫效果。
重排,就是當頁面元素的幾何屬性改變時,會導致頁面重新計算DOM樹,然後引發後續的一系列操作。
重繪,就是當頁面的顏色等屬性變化,只會重新計算樣式,然後執行合成操作,省略了DOM樹計算的過程,就減少了渲染引擎的壓力。
當然,如果你的修改即沒有觸發重排也沒有觸發重繪,那不就更快了嗎?
對,所以CSS動畫實現可以使用transform,只會在合成執行緒階段處理,如果使用了GUI加速,也不會影響主程序,渲染就更快了。
下面是一些減少重排重繪的方法:
- 使用class批量處理樣式,而不是頻繁操作style
- 避免使用table佈局
- 使用框架,框架使用虛擬DOM,通過演算法減少操作DOM的頻率
- 使用transform處理動畫等