Skip to content

2026-08-01

/ 10 分鐘閱讀

/ Web Performance Fundamentals

改善 FCP:延遲載入非關鍵 JavaScript

JavaScript 的解析阻塞特性

瀏覽器在解析 HTML 時,只要遇到 <script> 標籤,就會立刻開始下載它。一旦下載完成,瀏覽器會立即停下手邊所有事情,執行這段 JavaScript,包含:

  • 停止解析後續的 HTML
  • 停止任何新的渲染工作
  • 阻塞主執行緒

這就是 JavaScript 被稱為「解析阻塞(parser blocking)」的原因。在火焰圖中,你會看到 script 下載完成後,主執行緒進入 evaluate、compile、execute 的流程,在這段時間內頁面完全無法繼續進行任何其他工作。

async 與 defer 的差異

為了解決解析阻塞問題,HTML 提供了兩個屬性:

async:告訴瀏覽器在有空的時候開始下載這個 script,但下載一完成就立刻執行,不等任何其他事情。這樣做的問題是製造了一個競賽(race condition):如果 script 比 CSS 更快下載完,一樣會阻塞渲染;只有 CSS 先完成時才能避免問題,結果是否改善取決於運氣。

defer:告訴瀏覽器在有空的時候開始下載,但絕對不會在 DOMContentLoaded 之前執行。瀏覽器會等到所有 HTML 都解析完、即將觸發 DOMContentLoaded 之前,才統一執行所有被 defer 的 script。這樣無論 script 多快下載完,都不會阻塞渲染。

結論:大多數情況應該使用 deferasync 只在少數特殊情境才適合。

使用 defer 的幾個細節

執行順序的保證:如果有多個被 defer 的 script,它們的執行順序會依照在 HTML 文件中被發現的順序進行,而不是依照下載完成的順序。這讓有相依關係的 script 可以放心使用 defer。

type="module" 永遠是 defer:使用現代模組語法的 script(<script type="module">)預設就是 deferred,不需要另外加 defer,也無法取消這個行為。

放在 <head> 還是 </body> 之前? 講師的答案是:現在這個差別已經不太重要了。以前沒有 defer 時,開發者會把 script 放在 </body> 前來避免阻塞,但現在只要加上 defer,放在 <head> 裡讓瀏覽器提早發現並開始下載,反而更好。

defer 的 script 裡仍然可以監聽 DOMContentLoaded:因為 defer script 的執行時機是「在 DOMContentLoaded 觸發之前」,所以在 script 內部仍然可以用 addEventListener('DOMContentLoaded', ...) 來等待 DOM 準備好,這個模式完全可以繼續使用。

FCP 改善的三個策略小結

  1. 移除序列鏈:讓資源盡可能並行載入,避免因為依賴關係造成等待
  2. 預載關鍵路徑資源(preload):讓 FCP 必需的資源提早開始取得
  3. 延遲非關鍵 JavaScript(defer):讓不影響首次繪製的 script 不阻塞渲染

目標是讓 FCP 的路徑盡可能短,只包含絕對必要的資源。

複習

JavaScript 在瀏覽器解析過程中的主要問題是什麼?

JavaScript 具有解析阻塞的特性。當瀏覽器遇到 script 標籤時,會立即下載並執行該腳本,停止 HTML 解析、渲染與主執行緒上的所有工作。

async 與 defer 這兩個 script 屬性有什麼差異?

async 會在瀏覽器有空時開始下載腳本,但下載完成後立刻執行,可能造成競賽條件。defer 會下載腳本,但等到 DOMContentLoaded 觸發之前才執行,並維持多個 script 的執行順序。

當 script 標籤加上 type="module" 時會發生什麼事?

type="module" 的 script 預設永遠是 deferred,不需要另外加 defer,也無法取消這個行為。

script 標籤應該放在 HTML 文件的哪個位置?

使用 defer 後,script 標籤的位置變得比較不重要。放在 head 或 body 結束標籤之前都可以,使用 defer 可以讓瀏覽器自行決定何時開始下載並管理執行時機。

多個 defer script 如何維持執行順序?

defer script 會依照它們在文件中被發現的順序執行,即使下載完成的順序不同。第一個被發現的 script 會第一個執行,其餘依此類推。

小測驗

在 script 標籤上加入 defer 屬性會有什麼效果? 延遲腳本的執行,直到 DOMContentLoaded 觸發之前才執行
為什麼 JavaScript 被認為具有解析阻塞的特性? 遇到 script 時會停止解析後續內容與渲染工作
async 與 defer 這兩個 script 屬性的關鍵差異是什麼? async 下載完成後立刻執行,defer 則等到 DOMContentLoaded 之前才執行
使用 type="module" 屬性的 script 標籤會有什麼效果? 該 script 會自動被 defer,永遠延遲執行
同時有多個 defer script 時,它們的執行行為是什麼? 它們會維持在文件中被發現的原始順序來執行

此文章是 FrontendMasters 上的 Web Performance Fundamentals 課程筆記

最後更新時間:

Buy Me A Coffee

系列章節 第 33 篇 / 共 41 篇

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog
Photo by Federica Galli on Unsplash