網頁效能的度量與最佳化
🎯 核心問題
為什麼你的網頁載入很慢,使用者還在瘋狂抱怨卡頓? 這就像是問:為什麼餐廳上菜慢、顧客等得不耐煩?本章將帶你深入理解前端效能最佳化的核心概念,讓你的網頁「飛」起來。
1. 為什麼要「效能最佳化」?
1.1 從能用到好用:效能最佳化的演變
十年前的網頁非常簡單,一個頁面可能就幾 KB,載入速度幾乎感覺不到延遲。那時的我們根本不需要考慮效能最佳化——因為問題還沒出現。
但現在完全不同了。現代網頁的複雜度呈指數級增長:一個電商首頁可能有幾十張高清圖片,一個社群平台可能同時載入上千條動態,一個管理後台可能包含幾十個互動元件。這些「豐富」的功能背後,是龐大的程式碼量和資源體積,如果不好好最佳化,使用者體驗就會一塌糊塗。
👴 十年前的網頁
- 單個頁面只有幾 KB 到幾十 KB
- 只有文字和少量圖片
- 使用者幾乎感覺不到載入延遲
- 不需要任何效能最佳化
🚀 現代的網頁
- 單個頁面可能幾 MB 甚至更大
- 有高清圖片、影片、互動元件
- 載入慢、捲動卡、點擊反應遲鈍
- 必須做效能最佳化才能用
這就是「效能最佳化」要解決的問題:讓使用者等待的時間更短,讓操作更流暢。
1.2 一個真實的踩坑故事:為什麼你需要了解效能最佳化
你可能會說:「現在的網路這麼快,裝置這麼好,還需要考慮效能最佳化嗎?」讓我講一個真實的故事,你就會明白為什麼這些知識如此重要。
小王的效能踩坑記
小王是一個剛入職的前端工程師,負責開發公司的電商首頁。他用了最新的 Vue 3、最流行的 UI 函式庫,功能做得非常完善,自己在公司的高效能電腦上測試時一切正常。
但上線後第二天,客服部門就炸鍋了——大量使用者投訴說「網站太卡了」、「圖片載入不出來」、「點擊按鈕半天沒反應」。小王打開自己的開發機測試,一切都很流暢啊,他完全不理解問題出在哪裡。
後來請師傅幫忙定位,師傅讓他用一台普通的筆記型電腦,連上普通的 4G 行動網路,然後再測試自己的網站。小王這才傻眼了:首頁載入要等十幾秒,捲動列表時卡得像 PPT,點擊按鈕後要等好幾秒才有反應。
原來小王的開發環境是頂配的 MacBook Pro + 千兆光纖,而大多數使用者用的是普通裝置 + 行動網路。他寫的程式碼裡有幾十張未壓縮的高清圖片,引入了整個 UI 函式庫但只用了幾個元件,還在渲染時做了大量同步計算。
解決方案其實不複雜:壓縮圖片、按需引入元件、把計算放到背景執行緒、使用虛擬列表。這樣改動之後,首頁載入時間從十幾秒變成了 2 秒,捲動也非常流暢,使用者投訴立刻消失了。
小王從此明白了一個道理:不了解效能最佳化,你寫出來的程式碼在自己電腦上跑得飛快,但在使用者裝置上可能根本沒法用。
💡 核心啟示
效能最佳化不是可選項,而是必備技能。你要站在使用者的視角思考問題——他們用的是普通裝置、普通網路,如果你的程式碼在他們裝置上跑不動,那就說明你需要最佳化了。
2. 核心概念:載入、渲染、互動
🤔 這些概念和效能有什麼關係?
載入、渲染、互動就是使用者存取網頁的三個核心環節,每個環節都可能成為效能瓶頸。
當使用者存取你的網頁時,會依次經歷:
- 載入 → 把 HTML/CSS/JS/圖片 從伺服器下載到瀏覽器
- 渲染 → 把下載的內容「畫」成使用者能看到的頁面
- 互動 → 回應使用者的點擊、捲動等操作
所以,效能最佳化就是讓這三個環節都快起來。理解它們,你才能知道效能瓶頸出在哪裡,該用什麼方法最佳化。
在深入學習具體最佳化技巧之前,我們需要先搞清楚這幾個核心概念。為了幫助你更好地理解,我們用餐廳的比喻來類比它們之間的關係。
2.1 用餐廳比喻理解三個環節
想像你去一家餐廳吃飯,這個過程和存取網頁驚人地相似:
| 環節 | 🍽️ 餐廳比喻 | 實際作用 | 具體例子 |
|---|---|---|---|
| 載入 | 把食材從倉庫運送到廚房 | 把 HTML/CSS/JS/圖片 從伺服器下載到瀏覽器 | 使用者打開網頁,瀏覽器開始下載各種資源 |
| 渲染 | 廚師把食材加工成菜餚 | 瀏覽器把程式碼轉換成使用者能看到的頁面 | 瀏覽器解析 HTML、計算佈局、繪製頁面 |
| 互動 | 服務生回應顧客的需求 | 瀏覽器回應點擊、捲動等操作 | 使用者點擊按鈕,頁面做出回饋 |
2.2 載入(Loading):食材運送
載入是指把網頁所需的各種資源(HTML、CSS、JavaScript、圖片、字型等)從伺服器下載到瀏覽器的過程。這個過程就像把食材從倉庫運送到廚房,如果運送慢或者食材太多,廚房就得乾等著。
為什麼載入會慢? 主要有三個原因:首先,資源體積太大——一張未壓縮的高清圖片可能就有 5MB,相當於下載一本小說;其次,網路延遲——如果伺服器在國外,或者使用者用行動網路,每個請求都要等很久;最後,請求太多——瀏覽器同時下載的資源數量有限,太多資源就要排隊。
🔍 看看載入階段都做了什麼
當使用者在瀏覽器網址列輸入網址並按下 Enter 後,會依次發生:
- DNS 解析:把網域名稱(如
www.example.com)轉換成 IP 位址(如192.168.1.1),就像透過電話簿查詢餐廳地址 - TCP 連線:瀏覽器和伺服器建立連線,就像打電話前要先撥號
- TLS 握手:建立安全連線(HTTPS),就像確認對方身分
- 請求資源:瀏覽器向伺服器請求 HTML 檔案
- 解析 HTML:瀏覽器解析 HTML,發現需要 CSS、JS、圖片等資源,繼續請求
- 下載資源:把所有需要的資源下載到本地
- 開始渲染:下載完成後,開始渲染頁面
前面的 1-4 步叫「首字節時間」(TTFB),後面的 5-7 步是真正的資源下載時間。
常見的載入最佳化手段:
- 壓縮資源:把檔案變小(Gzip、Brotli 壓縮)
- 使用 CDN:把檔案存在離使用者更近的伺服器上
- 延遲載入:只載入使用者看得到的內容,剩下的等使用者捲動時再載入
- 程式碼分割:把大檔案拆成小檔案,按需載入
2.3 渲染(Rendering):廚師做菜
渲染是指瀏覽器把下載的 HTML、CSS、JavaScript 轉換成使用者能看到的頁面的過程。這個過程就像廚師把食材加工成菜餚,如果工序複雜、步驟多,上菜就會慢。
📖 什麼是「渲染」?
你可能聽說過「渲染」這個詞,它到底是什麼?
簡單來說,渲染就是把程式碼變成畫面的過程。
瀏覽器要做的事情包括:
- 解析 HTML → 生成 DOM 樹(頁面的結構)
- 解析 CSS → 生成 CSSOM 樹(頁面的樣式)
- 合併 → 生成渲染樹(結構和樣式的結合)
- 佈局 → 計算每個元素的位置和大小
- 繪製 → 把元素畫出來
- 合成 → 把多個圖層合併成最終畫面
這個過程非常複雜,任何一個環節出問題,都會導致頁面卡頓。
為什麼渲染會慢? 主要有兩個原因:首先,頁面太複雜——如果一個頁面有上萬個 DOM 節點,瀏覽器計算佈局和繪製就會非常耗時;其次,頻繁修改頁面——如果 JavaScript 程式碼頻繁修改 DOM,會導致瀏覽器反覆重新佈局和繪製,消耗大量效能。
📁 看看渲染階段都做了什麼
渲染的完整流程:
HTML (字串)
↓
[解析 HTML] → 生成 DOM 樹
↓
DOM 樹 (頁面結構)
CSS (樣式表)
↓
[解析 CSS] → 生成 CSSOM 樹
↓
CSSOM 樹 (頁面樣式)
DOM 樹 + CSSOM 樹
↓
[合併] → 生成渲染樹
↓
渲染樹 (要渲染的元素)
↓
[佈局 Layout] → 計算每個元素的位置和大小
↓
[繪製 Paint] → 填充顏色、繪製文字
↓
[合成 Composite] → 合併多個圖層
↓
最終畫面關鍵渲染路徑(Critical Rendering Path):瀏覽器要盡快把第一屏內容渲染出來,讓使用者覺得「網站很快」。這叫「關鍵渲染路徑最佳化」。
👇 動手看看: 下面這個演示展示了瀏覽器是如何渲染頁面的。點擊「下一步」,觀察渲染的各個階段:
⚠️ Common Bottlenecks
- Large assetsImages and JS bundles are not compressed, so downloads take longer.
- Too many requestsHTTP/1.1 head-of-line blocking makes resources wait in line.
- Network latencyThe server is physically far away, increasing RTT.
🚀 Solutions
- Asset compressionUse Gzip/Brotli and image formats such as WebP.
- Lazy loadingLoad only resources visible in the current viewport.
- CDN accelerationDistribute assets to nodes close to users.
- HTTP cachingUse browser cache to avoid repeated requests.
常見的渲染最佳化手段:
- 減少重排和重繪:避免頻繁修改 DOM,使用
transform和opacity代替top和width - 虛擬列表:只渲染可見區域的內容,大量資料時效能提升明顯
- CSS 動畫:用 CSS 動畫代替 JavaScript 動畫,效能更好
2.4 互動(Interaction):服務生回應
互動是指瀏覽器回應使用者操作(點擊、捲動、輸入等)的過程。這個過程就像服務生回應顧客的需求,如果服務生忙不過來,顧客就得等。
為什麼互動會卡? 主要原因是主執行緒被阻塞了。瀏覽器的 JavaScript 是單執行緒的,如果程式碼在執行複雜的計算,就沒法回應使用者的操作,導致頁面卡頓。
🤔 什麼是「主執行緒」?
瀏覽器有多個執行緒,但負責執行 JavaScript、渲染頁面、回應使用者操作的只有一個——主執行緒。
你可以把主執行緒想像成一個忙碌的服務生,他要做很多事情:
- 執行 JavaScript 程式碼(計算資料、呼叫 API)
- 渲染頁面(佈局、繪製)
- 回應使用者操作(點擊按鈕、捲動頁面)
問題來了:他只有一個人。如果他在執行複雜的 JavaScript 計算(比如處理一萬條資料),這時候使用者點擊了按鈕,他是沒法立即回應的,必須等計算完才行。這就是卡頓的根源。
解決方案:
- 把複雜的計算放到 Web Worker(背景執行緒)
- 使用時間切片,把大任務拆成小任務
- 避免同步的複雜操作,改用非同步
👇 動手試試看: 下面這個演示對比了同步計算和 Web Worker 的區別。點擊「開始計算」,觀察頁面是否卡頓:
常見的互動最佳化手段:
- 防抖和節流:限制事件的觸發頻率(比如捲動事件、輸入事件)
- Web Worker:把複雜計算放到背景執行緒,不阻塞主執行緒
- 時間切片:把大任務拆成小任務,讓瀏覽器有機會回應使用者操作
3. 實戰:一個團隊的效能最佳化演進之路
講了這麼多概念,讓我們看一個真實的案例:某新創公司是如何從「完全沒考慮效能」一步步進化到「系統化效能最佳化」的。透過這個案例,你會更直觀地理解效能最佳化到底解決了什麼問題。
3.1 演進的全景圖
下面這張表展示了效能最佳化的四個階段,你可以看到最佳化手段、工具、指標是如何一步步進化的:
| 階段 | 最佳化手段 | 監控工具 | 核心指標 | 核心變化 |
|---|---|---|---|---|
| 階段一:原始時代 | 無(沒考慮) | 無(憑感覺) | 無 | 完全沒效能意識,能跑就行 |
| 階段二:手動最佳化 | 壓縮圖片、減少請求 | 瀏覽器 Network 面板 | 頁面載入時間 | 開始有意識,但方法原始 |
| 階段三:系統化最佳化 | 程式碼分割、延遲載入、虛擬列表 | Lighthouse、Performance 面板 | FCP、LCP、TBT | 用專業工具,有明確的最佳化目標 |
| 階段四:持續最佳化 | 效能預算、CI/CD 檢查 | RUM、Lighthouse CI | INP、CLS、全鏈路監控 | 把效能納入開發流程 |
📊 從表格中你能看到什麼?
讓我們逐行解讀這張表:
階段一 → 階段二:從「沒意識」到「有意識」。這是關鍵的一步——開發者開始意識到效能是個問題,並且嘗試最佳化。但最佳化手段比較原始,主要靠感覺和經驗。
階段二 → 階段三:從「手動」到「系統化」。這是質的飛躍——開始使用專業工具(Lighthouse、Performance 面板)來診斷效能問題,用科學的方法(程式碼分割、延遲載入)來最佳化,而不是憑感覺。
階段三 → 階段四:從「一次性最佳化」到「持續最佳化」。當效能最佳化成為開發流程的一部分後,就需要建立監控體系(RUM、真實使用者監控),在開發階段就設定效能預算,防止退化。
總結一下:效能最佳化演進不只是「用了更多技術」,而是整個思維方式的升級——從被動回應到主動預防,從憑感覺到資料驅動,從單次最佳化到持續改進。
3.2 階段一:原始時代——完全沒考慮
為什麼叫「原始時代」?因為這個階段完全沒考慮效能問題——能跑就行。團隊只有 3 個人,做一個簡單的企業官網,專案很小,看起來沒什麼問題。
但隨著專案變大、使用者增多,問題開始暴露出來。
開發方式:
- 最佳化手段:無,直接開發,沒考慮效能
- 監控工具:無,憑感覺判斷快慢
- 核心指標:無
這個階段的特點:
- ✅ 優點:開發快,沒有額外的學習成本
- ❌ 缺點:使用者體驗差,網速慢時根本沒法用
查看當時的問題
遇到的具體問題:
- 圖片太大:產品經理上傳了一張 5MB 的首頁 Banner 圖,行動網路使用者打開網頁要等 1 分鐘
- 沒有壓縮:CSS 和 JS 檔案完全沒有壓縮,體積是壓縮後的 3 倍
- 沒有快取:每次存取都要重新下載所有資源,老使用者也要等
- 同步載入:所有 JS 檔案都在
<head>中同步載入,阻塞頁面渲染
使用者的回饋:
- 「你們網站怎麼打不開?」
- 「圖片半天載入不出來,就是空白」
- 「點擊按鈕沒反應,是不是網站壞了?」
當時的臨時解決方案:
<!-- 用 loading 遮罩「欺騙」使用者 -->
<div id="loading">載入中...</div>
<script>
// 頁面載入完成後才移除遮罩
window.onload = function() {
document.getElementById('loading').style.display = 'none'
}
</script>這完全是在「自欺欺人」——頁面還是很慢,只是使用者看不到而已。
3.3 階段二:手動最佳化——開始有意識
原始時代的問題累積到一定程度,團隊終於決定開始做效能最佳化。這是一個重要的轉折點——從「完全不考慮」到「有意識地最佳化」。
但這個階段的最佳化比較原始,主要靠壓縮圖片、合併檔案等簡單手段。
開發方式:
- 最佳化手段:手動壓縮圖片、合併 CSS/JS 檔案、減少 HTTP 請求
- 監控工具:瀏覽器 Network 面板、簡單的計時日誌
- 核心指標:頁面載入時間(手動用碼錶計時)
這個階段的特點:
- ✅ 優點:有明顯改善,使用者不再瘋狂投訴
- ❌ 缺點:最佳化不系統,容易反覆,缺少量化指標
查看手動最佳化的具體做法
手動最佳化手段:
手動壓縮圖片:
- 用 Photoshop 把每張圖片手動「另存為 Web 格式」
- 把 PNG 轉 JPEG(有損壓縮,但體積小很多)
- 縮小圖片尺寸(比如 2000px 寬的圖縮小到 800px)
手動合併檔案:
html<!-- 最佳化前:10 個 JS 檔案 = 10 個請求 --> <script src="utils.js"></script> <script src="api.js"></script> <script src="component-a.js"></script> <script src="component-b.js"></script> ...(還有 6 個) <!-- 最佳化後:1 個合併的 JS 檔案 = 1 個請求 --> <script src="all.js"></script>把 CSS/JS 移到頁面底部:
html<body> <!-- 頁面內容 --> <h1>歡迎訪問</h1> <!-- 最佳化:把 CSS/JS 放在最後 --> <link rel="stylesheet" href="style.css"> <script src="app.js"></script> </body>
帶來的改善:
- 圖片體積從 5MB 減小到 500KB(減少 90%)
- HTTP 請求數從 30 個減少到 5 個
- 頁面載入時間從 30 秒減少到 8 秒
新的痛點:
- 手動工作量大:每次更新都要手動壓縮圖片、合併檔案
- 容易忘記:新人不知道要最佳化,直接上傳原圖
- 缺少量化:只知道「快了一些」,但不知道具體快多少
3.4 階段三:系統化最佳化——用工具和資料說話
階段二的問題(手動工作量大、缺少量化)困擾了團隊很久。直到後來,團隊發現了 Lighthouse、Performance 面板等專業工具,進入了系統化最佳化時代。
這個階段的核心是用資料驅動最佳化——先用工具診斷問題,找到效能瓶頸,再有針對性地最佳化。
開發方式:
- 最佳化手段:程式碼分割、延遲載入、虛擬列表、圖片自動壓縮
- 監控工具:Lighthouse、Chrome Performance 面板、WebPageTest
- 核心指標:FCP(首屏時間)、LCP(最大內容繪製)、TBT(總阻塞時間)
系統化最佳化的具體做法
使用 Lighthouse 診斷問題:
Lighthouse 是 Google 開發的自動化效能測試工具,可以給出全面的效能報告和最佳化建議。
# 使用 Lighthouse 測試網頁
lighthouse https://www.example.com --viewLighthouse 會給出:
- 效能評分(0-100 分)
- 核心指標(FCP、LCP、CLS、TBT、INP)
- 最佳化建議(比如「啟用文字壓縮」、「移除未使用的 JavaScript」)
關鍵指標解讀:
| 指標 | 全稱 | 含義 | 理想值 |
|---|---|---|---|
| FCP | First Contentful Paint | 首次內容繪製時間(使用者看到第一塊內容的時間) | <1.8s |
| LCP | Largest Contentful Paint | 最大內容繪製時間(主要內容載入完成的時間) | <2.5s |
| TBT | Total Blocking Time | 總阻塞時間(主執行緒被阻塞的總時間) | <200ms |
| CLS | Cumulative Layout Shift | 累積佈局偏移(頁面元素亂跳的程度) | <0.1 |
這個階段的特點:
- ✅ 優點:最佳化有針對性,效果好,有量化指標
- ❌ 缺點:需要學習工具和指標,有一定門檻
查看系統化最佳化的具體技術
1. 程式碼分割(Code Splitting):
把大檔案拆成小檔案,按需載入。比如使用者訪問首頁時,只載入首頁需要的程式碼,等到點擊「關於我們」時,再去載入關於頁面的程式碼。
// 最佳化前:所有程式碼都在一個檔案,一次性載入
import About from './views/About.vue'
import Contact from './views/Contact.vue'
// ... 還有 10 個頁面
// 最佳化後:延遲載入,訪問時才載入
const About = () => import('./views/About.vue')
const Contact = () => import('./views/Contact.vue')效果:首頁載入的程式碼量減少 70%,首屏時間從 5 秒降到 1.5 秒。
2. 圖片延遲載入(Lazy Loading):
只載入使用者看得到的圖片,捲動到可視區域時再載入其他圖片。
<!-- 現代瀏覽器支援原生的延遲載入 -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" />效果:首頁載入的圖片數量從 20 張減少到 3 張,節省 80% 的頻寬。
3. 虛擬列表(Virtual Scrolling):
如果要渲染 10,000 條資料,不要真的建立 10,000 個 DOM 節點,而是只渲染可見區域的 20 條,捲動時動態替換。
<!-- 使用 vue-virtual-scroller 元件 -->
<RecycleScroller
:items="items"
:item-size="50"
key-field="id"
>
<template #default="{ item }">
<div>{{ item.name }}</div>
</template>
</RecycleScroller>效果:10,000 條資料從「卡死」變成「流暢捲動」,記憶體佔用減少 95%。
3.5 階段四:持續最佳化——把效能納入開發流程
當工具和方法成熟後,團隊開始關注更深層次的問題:如何防止效能退化?如何讓效能成為開發流程的一部分?
這個階段的核心是建立效能監控和預算體系——不是上線後再最佳化,而是在開發階段就預防效能問題。
開發方式:
- 最佳化手段:效能預算(Performance Budget)、Lighthouse CI、真實使用者監控(RUM)
- 監控工具:Lighthouse CI、WebPageTest API、Google Analytics
- 核心指標:INP(互動延遲)、CLS(佈局偏移)、全鏈路監控
持續最佳化的具體做法
1. 設定效能預算:
在打包配置中設定限制,超過就報錯,防止「無意中引入大檔案」。
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
// 限制單個檔案不超過 200KB
chunkFileNames: 'js/[name]-[hash].js',
}
},
// 超過 200KB 時發出警告
chunkSizeWarningLimit: 200
}
})2. Lighthouse CI:
每次提交程式碼時,自動執行 Lighthouse 測試,如果效能分數下降,就阻止合併。
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
https://staging.example.com
budgetPath: ./budget.json3. 真實使用者監控(RUM):
在真實使用者瀏覽器中收集效能資料,而不是只在開發環境測試。
// 傳送效能資料到伺服器
const perfData = performance.getEntriesByType('navigation')[0]
const lcp = performance.getEntriesByType('largest-contentful-paint')[0]
fetch('/api/perf', {
method: 'POST',
body: JSON.stringify({
fcp: perfData.loadEventEnd - perfData.fetchStart,
lcp: lcp.renderTime || lcp.loadTime,
url: window.location.href
})
})效果:
- 能及時發現效能退化(比如某次提交導致 LCP 從 2 秒變成 5 秒)
- 能了解真實使用者的體驗(而不是開發環境的「理想狀態」)
- 能針對性地最佳化最慢的那 10% 使用者
這個階段會做什麼?
- 效能預算:限制檔案大小、請求數量,超過就報警
- CI/CD 檢查:每次提交程式碼自動測試效能,退化就阻止合併
- 真實使用者監控:收集真實使用者的效能資料,持續改進
- 定期效能報告:每週/每月生成效能報告,追蹤趨勢
4. 常見效能瓶頸與解決方案
講了這麼多理論,讓我們看看實際開發中最常見的效能問題,以及如何解決。
4.1 圖片載入慢
問題表現:圖片半天載入不出來,或者載入過程中頁面跳動。
原因:
- 圖片體積太大(高清原圖)
- 圖片尺寸太大(2000px 寬的圖顯示為 200px)
- 沒有延遲載入(一次性載入所有圖片)
解決方案:
- 使用現代圖片格式(WebP、AVIF):
<!-- 現代:WebP 格式,體積小 30-70% -->
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="圖片">
</picture>- 響應式圖片(根據裝置大小載入不同尺寸):
<!-- 小裝置載入小圖,大裝置載入大圖 -->
<img
src="image-800.jpg"
srcset="image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1200px"
alt="響應式圖片">- 延遲載入(使用者捲動到時再載入):
<!-- 現代:原生延遲載入 -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" />👇 動手試試看: 下面這個演示對比了延遲載入和不延遲載入的區別。觀察網路請求:
Detailed Comparison
| Format | Size | Quality | Transparency | Animation | Recommendation |
|---|---|---|---|---|---|
| JPEG | Medium | Good | ✗ | ✗ | ★★★★☆ |
| PNG | Large | Perfect | ✓ | ✗ | ★★★★★ |
| WebP | Small | Excellent | ✓ | ✓ | ★★★★★ |
| AVIF | Smallest | Outstanding | ✓ | ✗ | ★★★★★ |
Optimization Tips
- Prefer WebP to reduce size by 30-50%.
- Provide JPEG/PNG fallbacks for older browsers.
- Use the <picture> element for automatic fallback.
- Use JPEG for photos and PNG or SVG for icons.
Recommended Tools
- Squoosh: open-source image compression from Google.
- ImageOptim: image optimization tool for macOS.
- TinyPNG: online smart compression with WebP support.
- Sharp: Node.js image processing library for automation.
4.2 首屏載入慢
問題表現:使用者打開網頁,白屏時間很長。
原因:
- 載入了太多不必要的程式碼
- 關鍵渲染路徑被阻塞
- 沒有做程式碼分割
解決方案:
- 程式碼分割(Code Splitting):
// 路由延遲載入:訪問時才載入
const routes = [
{
path: '/about',
component: () => import('./views/About.vue') // 訪問 /about 時才載入
}
]- 預載入關鍵資源(Preload):
<!-- 提前告知瀏覽器:這些資源很重要,優先載入 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.jpg" as="image">- 內聯關鍵 CSS:
<!-- 把首屏需要的 CSS 直接內嵌在 HTML 中 -->
<style>
/* 首屏關鍵樣式 */
.hero { background: #000; color: #fff; }
</style>4.3 捲動卡頓
問題表現:頁面捲動時一卡一卡的,不流暢。
原因:
- 渲染了太多 DOM 節點(比如 10,000 條資料)
- 捲動事件監聽器中有複雜計算
- 頻繁觸發佈局計算
解決方案:
- 虛擬列表(Virtual Scrolling):
<!-- 只渲染可見區域的內容 -->
<RecycleScroller
:items="10000"
:item-size="50"
>
<template #default="{ item }">
<div>{{ item.name }}</div>
</template>
</RecycleScroller>👇 動手看看: 下面這個演示對比了普通列表和虛擬列表的效能差異:
- 節流捲動事件(Throttle):
// 限制捲動事件的觸發頻率(最多每 100ms 觸發一次)
const throttledScroll = throttle(() => {
updatePosition()
}, 100)
window.addEventListener('scroll', throttledScroll)- 使用 CSS
will-change:
/* 提前告知瀏覽器:這個元素會變化,請做好準備 */
.scroll-container {
will-change: transform;
}4.4 點擊反應慢
問題表現:點擊按鈕後,要等好幾秒才有反應。
原因:
- 點擊事件處理器中有複雜計算(阻塞主執行緒)
- 沒有使用防抖(使用者快速點擊多次,觸發多次計算)
解決方案:
- 防抖點擊事件(Debounce):
// 使用者停止點擊 300ms 後才執行
const debouncedClick = debounce(() => {
submitForm()
}, 300)
button.addEventListener('click', debouncedClick)- 使用 Web Worker(把計算放到背景執行緒):
// 主執行緒
const worker = new Worker('calculator.js')
button.addEventListener('click', () => {
worker.postMessage({ data: largeData })
})
worker.onmessage = (e) => {
// 計算完成,顯示結果
showResult(e.data.result)
}
// calculator.js (Worker 執行緒)
self.onmessage = (e) => {
const result = heavyCalculation(e.data.data)
self.postMessage({ result })
}5. 效能監控工具
效能最佳化不是一次性工作,需要持續監控。下面介紹常用的工具。
5.1 瀏覽器開發者工具
Chrome DevTools 是最常用的效能分析工具:
- Network 面板:查看資源載入情況
- Performance 面板:分析執行時效能(FPS、主執行緒活動)
- Lighthouse:一鍵生成效能報告
如何使用 Performance 面板
- 打開 Chrome DevTools(F12)
- 切換到 Performance 面板
- 點擊「Record」按鈕
- 操作網頁(捲動、點擊等)
- 點擊「Stop」停止錄製
- 分析結果:看 FPS(幀率)、主執行緒活動、長任務等
5.2 Lighthouse
Lighthouse 是 Google 開發的自動化效能測試工具:
# 命令列使用
lighthouse https://www.example.com --view
# 或者在 Chrome DevTools 中使用
# 打開 DevTools → Lighthouse → 點擊 "Analyze page load"Lighthouse 會給出:
- 效能評分(0-100 分)
- 核心指標(FCP、LCP、CLS、TBT、INP)
- 最佳化建議(按影響排序)
5.3 WebPageTest
WebPageTest 是線上效能測試工具,可以從多個地點、多種裝置測試:
# 訪問 https://www.webpagetest.org
# 輸入網址,選擇測試地點和裝置,點擊 "Start Test"WebPageTest 會給出:
- 瀑布圖(Waterfall):每個資源載入的時間線
- 影片對比:最佳化前後的載入過程影片
- 最佳化建議
6. 效能最佳化清單
下面是一個實用的效能最佳化清單,你可以按照這個順序最佳化你的網頁:
6.1 載入最佳化
- ✅ 壓縮圖片:使用 WebP 格式,壓縮品質 80-85%
- ✅ 響應式圖片:根據裝置大小載入不同尺寸的圖片
- ✅ 延遲載入:圖片和元件延遲載入,只載入可見內容
- ✅ 程式碼分割:按路由分割程式碼,按需載入
- ✅ 壓縮程式碼:啟用 Gzip/Brotli 壓縮
- ✅ 使用 CDN:把靜態資源放到 CDN,加速下載
- ✅ 預載入關鍵資源:使用
<link rel="preload">
6.2 渲染最佳化
- ✅ 減少重排重繪:使用
transform和opacity代替top和width - ✅ 虛擬列表:大量資料時使用虛擬捲動
- ✅ CSS 動畫:優先使用 CSS 動畫,而不是 JavaScript 動畫
- ✅ 最佳化關鍵渲染路徑:內聯關鍵 CSS,延遲載入非關鍵 CSS
- ✅ 避免 @import:
@import會阻塞渲染,改用<link>
6.3 互動最佳化
- ✅ 防抖和節流:捲動、輸入、resize 事件使用防抖/節流
- ✅ Web Worker:複雜計算放到背景執行緒
- ✅ 時間切片:大任務拆成小任務,避免長任務
- ✅ 避免同步佈局:不要在迴圈中讀取佈局屬性(如
offsetHeight)
6.4 快取最佳化
- ✅ HTTP 快取:配置 Cache-Control 和 ETag
- ✅ Service Worker:快取靜態資源,實現離線訪問
- ✅ LocalStorage:快取 API 資料,減少請求
- ✅ 記憶體快取:使用
Map/Object快取計算結果
6.5 監控最佳化
- ✅ Lighthouse CI:每次提交程式碼自動測試效能
- ✅ 真實使用者監控:收集真實使用者的效能資料
- ✅ 效能預算:設定檔案大小限制,超過報警
- ✅ 定期效能報告:每週/每月生成效能趨勢報告
7. 總結
讓我們用一張表格來回顧前端效能最佳化的核心概念:
| 概念 | 一句話解釋 | 解決的問題 | 常用手段 |
|---|---|---|---|
| 載入最佳化 | 讓資源下載更快 | 首屏慢、等待時間長 | 壓縮圖片、CDN、程式碼分割、延遲載入 |
| 渲染最佳化 | 讓頁面「畫」得更快 | 捲動卡、點擊慢 | 虛擬列表、減少重排重繪、CSS 動畫 |
| 互動最佳化 | 讓回應更快 | 點擊沒反應、操作卡頓 | 防抖節流、Web Worker、時間切片 |
| 快取最佳化 | 避免重複下載 | 重複訪問慢 | HTTP 快取、Service Worker、LocalStorage |
| 監控最佳化 | 持續發現問題 | 效能退化 | Lighthouse、RUM、效能預算 |
寫在最後
效能最佳化是一個持續演進的話題,工具會變,但核心理念不變:站在使用者的角度思考問題,讓等待時間更短、讓操作更流暢。
理解了這些基本原理,無論技術如何更新換代,你都能快速上手、從容應對。
希望這篇文章能幫助你建立起對前端效能最佳化的整體認知。當你在實際專案中遇到效能問題時,能夠知道從哪裡入手、如何定位、怎樣解決。