當我們建構的網站越來越依賴 JavaScript 時,有時會以我們不一定能輕易察覺的方式,為我們傳送的內容付出代價。本文將說明,如果想讓網站在行動裝置上快速載入並提供互動功能,為什麼需要自律。傳送較少的 JavaScript,代表網路傳輸時間、解壓縮程式碼的時間,以及剖析和編譯 JavaScript 的時間都會減少。
網路
大多數開發人員在考量 JavaScript 的成本時,都會從下載和執行成本的角度思考。使用者的連線速度越慢,透過網路傳送更多位元組的 JavaScript 所需的時間就越長。

這可能會造成問題,因為使用者實際的網路連線類型可能並非 3G、4G 或 Wi-Fi。您可以使用咖啡店的 Wi-Fi,但連線至 2G 速度的行動熱點。
您可以透過下列方式降低 JavaScript 的網路傳輸成本:
- 只傳送使用者需要的程式碼。
- 壓縮
- 使用 UglifyJS 壓縮 ES5 程式碼。
- 使用 babel-minify 或 uglify-es 壓縮 ES2015+。
- 壓縮
- 移除未使用的程式碼。
- 使用開發人員工具程式碼涵蓋率,找出可移除或延遲載入的程式碼。
- 使用 babel-preset-env 和 browserlist,避免轉譯新式瀏覽器已有的功能。進階開發人員可以仔細分析 webpack 組合,找出可移除不必要依附元件的機會。
- 如要移除程式碼,請參閱 tree-shaking、Closure Compiler 的進階最佳化功能,以及程式庫修剪外掛程式,例如 lodash-babel-plugin 或 webpack 的 ContextReplacementPlugin (適用於 Moment.js 等程式庫)。
- 快取程式碼,盡可能減少網路傳輸。
剖析/編譯
下載後,JavaScript 的最重成本之一,就是 JS 引擎剖析/編譯這段程式碼的時間。在 Chrome 開發人員工具中,剖析和編譯是「Performance」面板中黃色「Scripting」時間的一部分。

「Bottom-Up」和「Call Tree」分頁會顯示確切的剖析/編譯時間:
但這為何如此重要?

如果剖析/編譯程式碼的時間過長,使用者可能需要等待很久才能與網站互動。傳送的 JavaScript 越多,網站互動前剖析和編譯所需的時間就越長。
「以位元組為單位,瀏覽器處理 JavaScript 的成本高於處理相同大小的圖片或網頁字型」- Tom Dale
與 JavaScript 相比,處理相同大小的圖片會產生許多成本 (仍須解碼!),但在一般行動硬體上,JS 更有可能對網頁的互動性造成負面影響。
當我們說剖析和編譯速度緩慢時,背景資訊很重要,我們在這裡討論的是平均手機。一般使用者的手機可能配備速度緩慢的 CPU 和 GPU、沒有 L2/L3 快取,甚至可能記憶體受限。
網路功能和裝置功能不一定相符。即使使用者擁有出色的 Fiber 連線,也不一定有最好的 CPU 來剖析及評估傳送至裝置的 JavaScript。反之亦然,如果網路連線不佳,但 CPU 速度極快,— Kristofer Baxter,LinkedIn
下方顯示在低階和高階硬體上剖析約 1 MB 解壓縮 (簡單) JavaScript 的成本。市面上最快的手機和一般手機在剖析/編譯程式碼的時間上,有 2 到 5 倍的差異。
如果是實際網站,例如 CNN.com 呢?
在 iPhone 8 這類高階手機上,剖析/編譯 CNN 的 JS 只要約 4 秒,而一般手機 (Moto G4) 則需要約 13 秒。這可能會大幅影響使用者與網站完整互動的速度。
這凸顯了在「平均」硬體 (例如 Moto G4) 上進行測試的重要性,而不只是在您可能擁有的手機上進行測試。不過,情境很重要:請根據使用者的裝置和網路條件進行最佳化。
我們真的傳送了太多 JavaScript 嗎?呃,可能吧 :)
我們使用 HTTP Archive (前 50 萬個網站) 分析行動裝置上的 JavaScript 狀態,發現 50% 的網站需要超過 14 秒才能互動。這些網站最多會花 4 秒剖析及編譯 JS。

如果考量到擷取及處理 JS 和其他資源所需的時間,使用者可能需要等待一段時間,才會覺得網頁已準備就緒,這或許並不令人意外。我們絕對可以做得更好。
從網頁中移除不重要的 JavaScript,可縮短傳輸時間、減少 CPU 密集剖析和編譯作業,以及潛在的記憶體負擔。這也有助於加快網頁互動速度。
執行時間
不只是剖析和編譯會產生費用,JavaScript 執行 (剖析/編譯後執行程式碼) 是必須在主執行緒上執行的作業之一。執行時間過長也會延遲使用者與網站互動的時間。

如果指令碼執行時間超過 50 毫秒,Time to Interactive 就會延遲,延遲時間等於下載、編譯和執行 JS 的全部時間。 - Alex Russell
為解決這個問題,JavaScript 適合分成小區塊,避免鎖定主執行緒。看看是否能減少執行期間的工作量。
其他費用
JavaScript 也可能透過其他方式影響網頁效能:
- 記憶體。網頁可能會因 GC (垃圾收集) 而頻繁卡頓或暫停。瀏覽器回收記憶體時,JS 執行作業會暫停,因此頻繁收集垃圾的瀏覽器可能會比我們預期的更常暫停執行。避免記憶體流失和頻繁的 GC 暫停,確保網頁不會卡頓。
- 在執行階段,長時間執行的 JavaScript 可能會封鎖主執行緒,導致網頁沒有回應。將工作拆分為較小的部分 (使用
requestAnimationFrame()或requestIdleCallback()進行排程),可盡量減少回應速度問題,有助於改善 Interaction to Next Paint (INP)。
降低 JavaScript 傳送成本的模式
如要縮短 JavaScript 的剖析/編譯和網路傳輸時間,可以採用一些模式,例如以路徑為基礎的分塊或 PRPL。
PRPL
PRPL (推送、算繪、預先快取、延遲載入) 是一種模式,可透過積極的程式碼分割和快取,最佳化互動性:

我們來看看這項功能的效果。
我們使用 V8 的執行階段呼叫統計資料,分析熱門行動網站和漸進式網頁應用程式的載入時間。如圖所示,剖析時間 (以橘色表示) 占了這些網站的大部分時間:

Wego 網站使用 PRPL,成功縮短路徑剖析時間,快速提供互動功能。上述許多其他網站都採用程式碼分割和效能預算,試圖降低 JS 成本。
漸進式啟動
許多網站為了提高內容可見度,不惜犧牲互動性。如要在 JavaScript 套件較大時快速完成首次繪製,開發人員有時會採用伺服器端轉譯,然後在最終擷取 JavaScript 時「升級」以附加事件處理常式。
請注意,這項操作會產生費用。1) 您通常會傳送「較大」的 HTML 回應,這可能會延遲互動性;2) 使用者可能會處於詭異的狀態,在 JavaScript 處理完成之前,有一半的體驗實際上無法互動。
漸進式啟動可能是更好的方法。傳送功能最少的網頁 (僅由目前路徑所需的 HTML/JS/CSS 組成)。隨著更多資源抵達,應用程式可以延遲載入並解鎖更多功能。
根據檢視畫面中的內容載入程式碼,是最佳做法。PRPL 和漸進式啟動模式可協助達成這個目標。
結論
傳輸大小對低階網路至關重要。剖析時間對 CPU 繫結裝置來說很重要。因此,請務必降低這些指標。
許多團隊都成功採用嚴格的效能預算,降低 JavaScript 傳輸和剖析/編譯時間。請參閱 Alex Russell 的「Can You Afford It?: Real-world Web Performance Budgets」(實際網頁效能預算),瞭解行動裝置預算指南。
如果您要建構針對行動裝置的網站,請盡可能在代表性硬體上開發,保持 JavaScript 剖析/編譯時間低,並採用效能預算,確保團隊能夠監控 JavaScript 成本。
瞭解詳情
- Chrome Dev Summit 2017 - Modern Loading Best Practices
- JavaScript 啟動效能
- 解決網路效能危機 - Nolan Lawson
- 預算是否足夠?Real-world performance budgets — Alex Russell
- 評估網頁框架和程式庫 - Kristofer Baxter
- Cloudflare 實驗 Brotli 壓縮的結果 (請注意,品質較高的動態 Brotli 可能會延遲初始網頁轉譯,因此請仔細評估。建議改用靜態壓縮。
- Performance Futures — Sam Saccone