天天看點

浏覽器渲染頁面的學習筆記

一、浏覽器的程序與線程

JavaScript是單線程的,但是浏覽器是多線程的,當我們使用Chrome浏覽器打開一個網頁時,浏覽器會開啟浏覽器渲染程序來完成頁面的渲染,該程序又包括多個線程:

1)GUI渲染線程,負責渲染浏覽器界面元素,解析HTML、CSS、建構DOM樹和RenderObject樹、布局和繪制等,當界面需要回流或重繪時都會觸發該線程;

2)js線程,執行js代碼,與渲染線程互斥;

3)定時器觸發線程,浏覽器定時計數器,即setTimeout和setInterval所在的線程;

4)事件觸發線程,用來管理事件循環,當一個事件被觸發時,該線程就會把事件添加到待任務隊列的隊尾,等待js引擎的處理。

5)異步http請求線程,如當浏覽器遇到link/script/img等請求後,就會開啟該線程去加載資源檔案。

二、浏覽器請求HTML的過程

浏覽器渲染頁面的學習筆記
  1. 第一次通路:

    使用者在浏覽器中首次通路一個網址時,浏覽器首先會将該網址發送到DNS伺服器進行解析,解析完成後會傳回浏覽器一個ip位址,浏覽器擷取到該ip位址後,向伺服器發送該ip位址請求,伺服器收到請求後,讀取該網頁請求檔案,發送到用戶端浏覽器,浏覽器接收到伺服器傳回的HTML,進行顯示。

    如果伺服器硬碟中有該HTML,則直接傳回,即我們常了解的靜态網頁;否則,通過後端引擎讀寫資料庫,動态生成一個HTML,傳回給浏覽器。

  2. 第二次通路:

    使用者在浏覽器中第二次通路一個網址時

    1)浏覽器首先擷取資源中的header資訊,檢視是否命中強緩存(根據Cache-Control和Expires判斷,其中Cache-Control的優先級要更高),如果命中強緩存,且緩存結果有效,則浏覽器不與伺服器進行互動,直接從緩存中讀取資源;

    2)如果沒有命中強緩存,則浏覽器與伺服器進行互動,檢視是否命中協商緩存(根據Last-modified/If-Modified-Since和Etag/If-None-Match進行判斷,其中Etag的優先級更高),如果命中協商緩存,則伺服器傳回一個新的響應頭資訊和304狀态碼,不傳回資源,浏覽器直接從緩存中讀取資源;

    3)如果沒有命中協商緩存,則伺服器傳回新的響應頭資訊和資源以及200狀态碼。

知識點補充,關于緩存辨別(Response Headers):

緩存表示 類型-協定 特點
Expaires 強緩存-http 1.0 伺服器傳回該請求結果緩存的到期時間,要求伺服器與用戶端的時鐘保持嚴格的同步,如果不同步,則強制緩存直接失效
Cache-Control 強緩存-http 1.1 使用max-age指定元件被緩存多久,從請求開始在max-age時間内浏覽器使用緩存,之外的使用請求,以此消除Expires的限制
Last-Modified/If-modified-Since 協商緩存-http 1.0 傳回該資源檔案在伺服器最後被修改的時間
Etag/If-none-Match 協商緩存-http 1.1 傳回目前資源檔案的一個唯一辨別,通過比對該辨別判斷是否過期,相交于Last-Modified解決了:1.一些檔案可能會周期性更改,但其中的内容并不改變;2.If-modified-Since的粒度是秒級的,而某些檔案可能在秒以下的時間修改;3.某些伺服器不能精确得到檔案的修改時間

三、浏覽器渲染HTML的過程

浏覽器渲染頁面的學習筆記

在浏覽器拿到資源檔案後,浏覽器就會在記憶體條中開辟出一塊棧記憶體,用來給代碼的執行提供環境,同時配置設定一個主線程,對拿到的HTML檔案進行一行行的解析和執行代碼。

我了解的這個主線程就是GUI渲染線程和js線程的結合,而這兩個線程是互斥的,當一個線程在運作時,另一個線程就會挂起。

GUI渲染線程在解析文檔時,當遇到link/src/img等外部資源檔案請求時,就會開啟異步http請求線程,去加載這些檔案;當遇到JavaScript代碼時,就會交給JS線程去處理,此時渲染線程挂起。通過頭部link的方式引入的css的加載,是不會阻塞dom樹的渲染,因為是在不同的線程。

  1. GUI渲染線程自上而下執行解析HTML文檔,生成DOM樹;
  2. 當CSS資源請求完成後,解析CSS檔案(包含内聯式和外部請求到的檔案)生成CSSOM樹,再結合DOM樹和CSSOM樹生成渲染樹RenderObject樹,用于渲染。
  3. Layout,即回流,負責對RenderObject樹中的元素的尺寸、位置等計算;
  4. Painting,即重繪,負責繪制頁面的像素資訊;
  5. 渲染程序将預設的圖層和複合圖層交給GPU程序,GPU程序再将各個圖層合成,最後顯示出頁面。

根據以上的特點,可以進行性能優化,如:

1.盡可能早的提前引入css檔案,因為css檔案的加載不會阻塞dom樹的渲染;

2.css中引入的資源可以使用預加載,如link标簽中的rel="preload"屬性;

3.在dom和cssom渲染之後再加載js檔案,因為GUI渲染線程和js線程是互斥的,執行js檔案會阻塞渲染。

4.對資源進行合并壓縮,如js、css、圖檔等檔案。React項目通過webpack打包壓縮後生成的bundle.js檔案其實就是對資源的合并壓縮,這樣在請求js檔案時就隻需要發送一次http請求即可;

5.圖檔懶加載,即生成dom樹時不加載圖檔,等到渲染樹經過回流重繪,整個頁面加載完成之後,再單獨請求圖檔資源,如果區域有滾動條,則滾動到哪個區域再進行圖檔加載。

6.音視訊走流檔案。

7.因為每次操作dom都會導緻浏覽器的回流和重繪,而DOM元素實際是非常龐大的,是以可以通過減少對DOM的操作來進行性能優化。虛拟DOM的出現就是将DOM的對比放在js層,通過高效的diff算法計算,找到變更的DOM節點,更新真實DOM時隻更新變更的部分,避免對整顆DOM樹進行變更,進而提高渲染效率。

四、JS線程

渲染線程和js線程是互斥的,因為因為js代碼在執行時可能會引發回流和重繪,即如果修改dom元素屬性的同時進行渲染,那麼渲染線程前後獲得的元素資料可能會不一緻。

JS線程在執行時,需要其他幾個線程進行輔助。

浏覽器渲染頁面的學習筆記

執行js代碼時,js線程作為主線程,執行同步任務,當碰到異步任務時,就交給事件觸發線程,如果事件觸發線程中包含onclick、ajax、setTimeout請求等就交給異步http請求線程執行,其中setTimeout的計時任務是由定時器線程完成。當這些異步任務執行完畢,就會将結果放入任務隊列中,當js線程空閑時,就會執行任務隊列中的任務。這整個過程是不停的循環的,這個機制就叫事件循環機制Event Loop。

JS線程中的任務分為同步任務和異步任務,而異步任務又包括微任務(micro task)和宏任務(macro task),微任務的優先級要高于宏任務。

  • 微任務:Promise,process.nextTick;
  • 宏任務:包括整體代碼script,setTimeout,setInterval。
    浏覽器渲染頁面的學習筆記
    微任務和宏任務有着各自的任務隊列(Event Queue),主線程優先讀取微任務隊列中的任務。

五、v8引擎簡介

js線程的所有工作就是由js引擎完成的。Javascript本質上是一種解釋型語言,與編譯型語言不同的是它需要一邊執行一邊解析,而編譯型語言在執行時就已經完成編譯,執行速度會更快。是以V8引擎的主要目的就是為了提高Javascript的解析執行速度,V8使用C++開發。V8引擎的出現,為Virtual DOM的産生提供了大前提,因為Virtual DOM的本質是用JS模拟DOM結構,操作虛拟DOM,就是操作js對象。

繼續閱讀