CSS 和 JS 阻塞二三事

幻靈爾依發表於2020-01-22

都放假了吧,祝大家春節快樂,沒物件的都相親成功,有物件的也相親成功~

瀏覽器渲染步驟

網頁渲染過程:

CSS 和 JS 阻塞二三事

  1. 將 HTML 解析成 DOM Tree,同時將 CSS 解析成 CSSOM Tree
  2. 將 DOM Tree 和 CSSOM Tree 合併成 Render Tree
  3. 生成佈局 Layout ,計算 Render Tree 中元素的尺寸、位置
  4. 將 Render Tree 繪製成畫素點,最後顯示在螢幕上

上面的過程是由 GUI 渲染執行緒完成的。

瀏覽器核心(渲染程式)

瀏覽器內有多個程式,其中渲染程式被稱為瀏覽器核心,負責頁面渲染和執行 JS 指令碼等。渲染程式負責瀏覽器的解析和渲染,內部有 JS 引擎執行緒、 GUI 渲染執行緒、事件迴圈管理執行緒、定時器執行緒、HTTP 執行緒。

JS 引擎執行緒負責執行 JS 指令碼,GUI 渲染執行緒負責頁面的解析和渲染,兩者是互斥的,也就是執行 JS 的時候頁面是停止解析和渲染的。這是因為如果在頁面渲染的同時 JS 引擎修改了頁面元素,比如清空頁面,會造成後續頁面渲染的不必要和錯誤。而由於 JS 經常要操作 DOM ,就要涉及 JS 引擎執行緒和 GUI 渲染執行緒的通訊,而執行緒間通訊代價是非常昂貴的,這也是造成 JS 操作 DOM 效率不高的原因。

解析、渲染與阻塞

本文例子需要在控制檯切換到弱網且無緩衝模式下進行測試:

CSS 和 JS 阻塞二三事

瀏覽器的 HTML/CSS 的解析和渲染都屬於 GUI渲染執行緒,所以和 JS 引擎執行緒是互斥、阻塞的。下面從程式碼實際執行的角度分析瀏覽器解析和渲染的順序,以及互相間的阻塞關係:

CSS 阻塞

  1. css 檔案的下載和解析不會影響 DOM 的解析,但是會阻塞 DOM 的渲染。因為 CSSOM Tree 要和 DOM Tree 合成 Render Tree 才能繪製頁面。下面的 test1 在 css 下載並解析完成前是預設樣式, test2 在 css 下載並解析完成之前不會顯示:
<button class="btn btn-primary">test1</button>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css">
<div>test2</div>
複製程式碼
  1. css 檔案沒下載並解析完成之前,後續的 js 指令碼不能執行。下面的 alert('ok') 在 css 下載並解析完成之前不會彈出來:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css">
<script>
    alert('ok')
</script>
複製程式碼
  1. css 檔案的下載不會阻塞前面的 js 指令碼執行。下面的 alert('ok') 會在 css 下載完成前彈出:
<script>
    alert('ok')
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css">
複製程式碼

所以在需要提前執行不操作 dom 元素的 js 時,不妨把 js 放到 css 檔案之前。

js 阻塞

js 檔案的下載和解析會阻塞 GUI 渲染程式,也就是會阻塞 DOM 和 CSS 的解析和渲染。

  1. js 檔案沒下載並解析完成之前,後續的 HTML 和 CSS 無法解析:
    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
    <div>test</div>
複製程式碼
  1. js 檔案的下載不會阻塞前面 HTML 和 CSS 的解析:
    <div>test</div>
    <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
複製程式碼

需要注意的點

第一,GUI 渲染執行緒會盡可能早的將內容呈現到螢幕上,並不會等到所有的 HTML 都解析完成之後再去構建和佈局 Render Tree,而是解析完一部分內容就顯示一部分內容,同時,可能還在通過網路下載其餘內容。下面 test1 會在 js 檔案下載完成前渲染完成,而 test2 則會在 js 檔案下載並執行完之後渲染:

  <div>test1</div>
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
  <div>test2</div>
複製程式碼

第二,檔案的下載是不會被阻塞的,不管是 css 還是 js 檔案,瀏覽器的主執行緒會在頁面解析前開啟下載,所以就算在外部指令碼執行前刪除指令碼,指令碼也還是會下載。

<body>
  <script>
    document.body.remove()
  </script>  
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css">
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
</body>
複製程式碼

總結

總之,不管是下載 js 檔案還是 css 檔案,都會阻塞下面頁面的渲染,但不會阻塞前面的渲染,符合我們預期的編碼邏輯。

相關文章