1. 程式人生 > 其它 >小謝第67問:使用者輸入一個URL到頁面渲染完成具體發生了什麼?

小謝第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*256512*512的大小,然後生成點陣圖,優先渲染頁面的可視區域,這也是瀏覽器在渲染上做的優化。

渲染引擎會會維護一個光柵化執行緒池,一旦光柵化完成,就會將繪製指令交給瀏覽器程序。如果這一步操作使用GUI加速,那麼後續操作也會在GUI程序中操作,這樣就不會影響渲染程序,提高了繪製的速度。

八、繪製

合成執行緒將繪製點陣圖的指令提交給瀏覽器程序後,瀏覽器程序會呼叫它的viz 的元件根據繪製指令將他們繪製到記憶體中,然後在頁面顯示出來。

這樣整個過程就完成了。

九、重排和重繪

重排和重繪是面試中經常問到的一個知識點,如果開發中做動畫比較多,那瞭解這方面的內容可以優化動畫效果。

重排,就是當頁面元素的幾何屬性改變時,會導致頁面重新計算DOM樹,然後引發後續的一系列操作。

重繪,就是當頁面的顏色等屬性變化,只會重新計算樣式,然後執行合成操作,省略了DOM樹計算的過程,就減少了渲染引擎的壓力。

當然,如果你的修改即沒有觸發重排也沒有觸發重繪,那不就更快了嗎?

對,所以CSS動畫實現可以使用transform,只會在合成執行緒階段處理,如果使用了GUI加速,也不會影響主程序,渲染就更快了。

下面是一些減少重排重繪的方法:

  • 使用class批量處理樣式,而不是頻繁操作style
  • 避免使用table佈局
  • 使用框架,框架使用虛擬DOM,通過演算法減少操作DOM的頻率
  • 使用transform處理動畫等
既然許願了,就努力去實現