前端效能的三把量尺:Performance API、Chrome Performance、React Profiler
文字解析 · 1 支影片 · 產生於 2026-10-01 00:54
章節(11)
1. Outline
- 起點 · Frontend System Design: Performance API, Chrome, React Profiler
從「頁面很慢」這句沒有資訊量的話出發,依序引入三個測量工具,每個工具都由前一個工具答不出來的問題逼出來,最後收成「先量再優化」的行動守則。
2. YouTuber 的思維推導
Frontend System Design: Performance API, Chrome, React Profiler
I Code It · 13m12s · 字幕 en · vision=on (input 指定 vision=true;影片有 code、console.table 結果、Chrome performance 火焰圖與 React Profiler 輸出,作者多次指著畫面說「you can see here」,讀圖才看得懂數字對照。) · YouTube
預估 vs 實際耗時
| 階段 | 預估 | 實際 |
|---|---|---|
| fetch | 6s | 2s |
| segment | 56s | 5m00s |
| shot | 37s | 4s |
| analyze | 5m00s | 10m08s |
| render | 5s | – |
作者的起點是一句沒有資訊量的抱怨:「這頁很慢」。他先把它拆成三個可回答的問題——有多慢、哪裡慢、跟什麼比——藉此推出全片的前提:沒有測量的優化只是猜測,所以要先建立 baseline,改完再量一次。接著他不是一次丟出三個工具,而是讓每個工具都由前一個工具的**不足**逼出來:先用瀏覽器內建、零安裝的 performance.now() 量出「這個操作花多久」,發現一個總數看不出該改哪一步,於是換 mark / measure 把操作拆成有名字的 search / filter / aggregate;拆完仍然只知道「多久」而不知道「為什麼」,於是打開 Chrome Performance 錄下整段互動,在 main thread 上看見 long task 與 simulateHeavyWork,把「一個數字」變成「數字的出處」;再追問「那 React 自己呢」,用 React Profiler 只量在意的那棵子樹,得到約 30 毫秒。
最後他把 30 毫秒與先前量到的約 400 毫秒並排,直接推翻「大概是 React 慢」的直覺,證明三個工具誰也取代不了誰、各給拼圖的一塊。收尾時他補上使用守則(profiling 有 overhead,數字當相對比較用),並把「找到瓶頸之後怎麼改」明確劃出這支影片的範圍之外。整條推導其實是一條解析度由粗到細的路線:先確認有問題、再定位在哪、最後才進到框架內部。
推理鏈:每段留給下一段的線索
每一列是一段,箭頭後的字是這段留給下一段的線索。點段落標題跳到該段。
- 「頁面很慢」不是資訊 → 既然結論是「先量」,馬上冒出來的問題就是「用什麼量」,而且那個東西必須便宜到可以隨時重跑:不用安裝、不綁框架、按一下就有數字。
- Performance API:瀏覽器內建的碼錶 → 碼錶的用法講完了,但還沒真的按下去:接下來要把它套到一個真實的、會慢的操作上,看看第一個數字長什麼樣,以及那個數字本身到底算不算解決了問題。
- performance.now 量出第一條 baseline → 一個總數有了,但畫面上的 analyzeLogs 裡其實串著 searchLogs、filterLogs、aggregateLogs 三個步驟——總數沒辦法告訴我們該去改哪一個,碼錶必須拆細。
- mark / measure:把一個操作拆成有名字的步驟 → 現在知道慢的是 search 這一步了,但手上仍然只有「哪一步、多久」——那 8 毫秒(換成真實資料量就是幾百毫秒)究竟是花在 JavaScript 計算、layout 還是畫面繪製,完全看不出來。
- Performance API 的天花板:知道多久,不知道為何 → 剩下的問題變成:有沒有一個工具,能把整段互動期間瀏覽器做的每一件事都錄下來,而不是只量我親手夾住的那一段程式碼?
- Chrome Performance:錄下整段互動 → 錄是錄下來了,但一份十幾秒、疊了好幾條軌的時間軸本身也還不是答案——得從裡面把那四百毫秒定位到某一條軌、某一個函式上。
- 火焰圖指出:時間全花在 main thread → main thread 上的 JavaScript 被指認成主嫌了,但同一張截圖左側還有 Scheduler 與 Components 兩條 React 專屬的軌沒有讀——React 自己渲染那些結果花了多少時間,仍是一片空白,也就無法排除「是 React 渲染太多元件」這個可能。
- React Profiler:只量你在意的那棵子樹 → 手上多了 30 毫秒這個數字,但單獨看它沒有任何意義——它得跟先前量到的分析時間並排,才能回答「該不該去優化 React」。
- 三個數字擺在一起才有結論 → 結論看起來乾淨俐落,但它整個建立在這些數字可信的前提上;而測量本身會不會影響被測對象、開發模式下的數字能不能當真,到這裡都還沒有交代。
- Profiler 的數字怎麼讀才對 → 三個工具、三種數字、以及讀數字的規矩都齊了,剩下的是把它們收成一條可以照著做的行動守則。
- 總結:先量,再決定要優化什麼
3. 逐段說明
Frontend System Design: Performance API, Chrome, React Profiler
1. 「頁面很慢」不是資訊 0:00–1:38
作者指出效能是前端最神祕的題目:大家說「這頁很慢」「我們有效能問題」,但沒回答有多慢、哪裡慢、跟什麼比。沒有這些答案,優化就只是猜測——切 code、搬 web worker、換演算法,改完也不知道有沒有變好,甚至可能更糟。結論是先測量、建立 baseline,改完再測一次。他要示範三種測法,各自回答不同的問題。
推理因為讀者手上通常只有「這頁很慢」這句話,所以作者接著做的不是給工具,而是把這句話逼成三個必須有答案的問題:有多慢?哪一部分慢?跟什麼比?只要這三個問題還空著,任何優化動作事後都無法判定成敗——他甚至指出可能改到更慢卻不自知。由此推出全片的地基:先測量、建立 baseline,改動之後再測一次。
AI 補充三個問題裡最容易被忽略的是第三個「跟什麼比」。它的意思不是「跟別家網站比」,而是「跟改動前的自己比」,這正是 baseline 的定義。要讓兩次數字可比,測量必須是可重跑的同一段程式、同一批資料、同一台機器、同一種 build,否則差異可能全來自環境。作者這裡跳過了一步值得補上:既然流程是「量 → 改 → 再量」,測量本身就必須夠便宜、夠隨手可得,否則沒有人會真的跑第二次——這個隱含條件直接決定了他挑工具的順序。
術語:performance optimizationcode splittinglazy loading
2. Performance API:瀏覽器內建的碼錶 1:38–2:13
第一個工具是 Performance API。作者喜歡它的理由很單純:已經內建在瀏覽器裡,不用安裝、不依賴 React 或任何框架。它回答的是最基本的一個問題——這個操作花了多久。心智模型就是碼錶:開始前按下、結束時按停,中間的差就是 duration。
推理因為上一段把需求定成「先量、而且要能一再重跑」,所以作者接著挑的是成本最低的那個工具,而不是功能最強的。他用一句話把它的能力範圍框死——它只回答「這個操作花了多久」——再用碼錶的比喻把使用方式壓縮成兩個動作:開始前按下、結束時按停,中間的差就是 duration。
- RPerformance API:now、mark/measure、getEntries、PerformanceObserver→ 存+回想
- APerformance API 是碼錶:開始前按下、結束按停,差就是 duration——但夾住的程式必須是同步的→ 批判類比
AI 補充「Performance API」不是單一函式,而是掛在 window.performance 上的一整組介面:取時間的 now()、打點與命名的 mark() / measure()、把結果取回來的 getEntries 系列,以及非同步監聽用的 PerformanceObserver。碼錶比喻很好用,但有一個藏起來的前提:被夾住的那段程式必須是同步的。一旦中間出現 await、setTimeout 或任何非同步等待,「按停」的那一行就不在你以為的位置上,量到的會是「這段同步程式碼跑完為止」,而不是「這件事做完為止」。這個限制之後決定了它能量什麼、不能量什麼。
3. performance.now 量出第一條 baseline 2:13–3:30
用前一支影片的 log analyzer 當例子:使用者輸入關鍵字後,前端要分析約五萬筆 log。作法很直接——呼叫 analyzeLogs 之前用 performance.now() 取一個高解析時間戳,函式回來後再取一個,兩者相減就是 duration。作者強調這個數字本身解決不了問題,但它給了更重要的東西:baseline。之後把工作搬進 web worker 或改演算法,可以用同一段測量再跑一次做對照,不再靠猜。


推理因為上一段的心智模型只剩下開始與停止兩個動作,所以作者接著找的是最小的實作:呼叫 analyzeLogs 之前取一個高解析時間戳、函式回來後再取一個、相減得到 duration。他隨即補上一句關鍵的自我修正——這個數字本身解決不了問題——把重點從「數字」轉到「這是一條可以重跑的 baseline」:之後不論搬進 web worker 還是改演算法,都能用同一段測量再跑一次做對照。
- P量第一條 baseline:performance.now() 前後各取一次,相減得 duration,印出來→ 練習
- R為什麼用 performance.now() 不用 Date.now():單調遞增、頁面載入為原點、不受系統校時影響→ 存+回想
- Edemo:20,000 筆 log 的 analyzeLogs 印出 Log analysis took 9.50ms——數字小不代表方法沒用→ 存+演練
AI 補充畫面上的程式碼把這件事寫得很乾淨:`const start: DOMHighResTimeStamp = performance.now()`、`const result = analyzeLogs(logs, query)`、`const duration = performance.now() - start`,而檔案開頭的註解已經先招認了限制——「You learn *how long* it took — not *which stage* is slow」。有兩點作者沒說:第一,為什麼是 performance.now() 而不是 Date.now()——前者是單調遞增、以頁面載入為原點的高解析時鐘,不受系統時間校正影響,用 Date.now() 在跨時區同步或 NTP 校時的瞬間甚至可能量出負值;第二,瀏覽器基於 Spectre 類的側通道疑慮會把這個時鐘的解析度刻意調粗(毫秒以下只到微秒等級),所以量單次、極短的操作時要跑多次取中位數。至於瀏覽器裡印出的 `Log analysis took 9.50ms`,那是 demo 用的 20,000 筆資料,數字小不代表方法沒用:baseline 的價值在於「同一段測量能再跑一次」,不在絕對值大小。
4. mark / measure:把一個操作拆成有名字的步驟 3:30–5:44
單一操作用 performance.now 就夠,但實務上一個操作常包含好幾個小步驟——log analyzer 會先 search、再 filter、最後 aggregate。作者改用 performance.mark 在每個步驟前後打點(searchStart / searchEnd),再用 performance.measure 把兩點之間命名為 search,filter 與 aggregate 比照辦理,最後用 console.table 印成一張表。示範中 search 約 8 毫秒、filter 0.1、aggregate 0.2(因為 demo 資料量小;完整例子有一萬五千筆就明顯得多)。他最欣賞的是每次測量都有一個有意義的名字,比一串匿名時間戳好讀太多。


推理因為上一段只拿到一個總數,所以作者接著要的是「拆解」而不是「更準」。他用 performance.mark() 在每個步驟前後各打一個點(search-start / search-end),再用 performance.measure() 把兩點之間命名為 search,filter 與 aggregate 比照辦理,最後用 console.table() 一次印成表。他最看重的不是精度而是可讀性:每筆測量都有一個有意義的名字,比一串匿名時間戳好懂太多。
- Pmark/measure 把一個操作拆成有名字的步驟,getEntriesByType('measure') 撈回來丟 console.table→ 練習
- E拆解結果:search 8.20ms、filter 0.10ms、aggregate 0.20ms——96% 落在 search,這種傾斜才是拆解買到的→ 存+演練
- Rmeasure 的結果留在瀏覽器 performance timeline,Chrome 錄製工具也讀得到;now() 只活在區域變數→ 存+回想
- Rmark/measure 兩個坑:名稱打錯不報錯只安靜少一列;不清會累積→ 存+回想
AI 補充畫面上完整的寫法是 `performance.mark('search-start')` → 執行 → `performance.mark('search-end')` → `performance.measure('search', 'search-start', 'search-end')`,最後用 `performance.getEntriesByType('measure')` 把所有命名區段撈回來、map 成 name 與 duration 兩欄餵給 console.table()。這裡有一個作者沒點破、但決定了 measure 價值的差別:performance.now() 的結果只活在你的區域變數裡,而 measure 的結果會留在瀏覽器的 performance timeline 緩衝區裡,因此瀏覽器的效能記錄工具也讀得到同一批命名區段——同一份標註同時服務 console 和錄製工具。另外兩個實務坑:mark 與 measure 會不斷累積,同一顆按鈕多按幾次後 getEntriesByType 會撈到愈來愈多筆,需要 clearMarks() / clearMeasures() 清掉;而且這些字串名稱要自己維護,打錯不會報錯,只會安靜地少一列。至於表上的數字——search 8.20ms、filter 0.10ms、aggregate 0.20ms,總計 8.5ms——絕對值不重要,重要的是比例:時間有 96% 落在 search,另外兩步加起來不到 4%,這種一眼可見的傾斜才是拆解真正買到的東西。
術語:performance.getEntriesByType()
5. Performance API 的天花板:知道多久,不知道為何 5:44–6:11
作者點出一個重要限制:Performance API 只告訴我們某件事花了多久,不告訴我們為什麼。搜尋花兩百毫秒是有用的資訊,但那是 JavaScript?是 React?是 layout 還是 rendering?這個 API 回答不了。要回答,就需要 profiler。
推理因為上一段已經把操作拆到步驟層級、卻仍然只得到「多久」,所以作者接著把問題本身換掉:從 how long 換成 why。他用一個假設數字示範這個換位——搜尋花了兩百毫秒是有用的資訊,但那是 JavaScript?是 React?是 layout 還是 rendering?——並直接宣告這個 API 回答不了,要回答就需要 profiler。
- CPerformance API 的天花板:只知道多久(你的程式碼從哪行到哪行),不知道為何(JS?layout?paint?)→ 畫地圖
- R「Performance API 答不了為什麼」不絕對:PerformanceObserver + LoAF 可部分歸因→ 存+回想
AI 補充根本原因在於量測的視角不同:mark / measure 量的是「你的程式碼從哪一行到哪一行」,它對瀏覽器在同一段時間裡做的樣式計算、版面配置、繪製一無所知。同一個兩百毫秒,可能是一個 JS 迴圈,也可能是你改了一個 class 觸發整頁 reflow,在 measure 眼裡長得一模一樣。要區分,需要的是一個站在瀏覽器那一側、能列出每個 task 與呼叫堆疊的工具,也就是 profiler。不過作者這裡的斷言在今天已經沒有那麼絕對(見本段勘誤):PerformanceObserver 搭配 long-animation-frame、layout-shift、event 等 entry type,其實能給出部分歸因,只是給不出 call stack——「知道 layout 佔了多久」和「知道是哪一行程式碼觸發的」仍是兩件事。
術語:Long Animation Frames API
「Is it layout or is it rendering? The performance API cannot answer these questions.」
6. Chrome Performance:錄下整段互動 6:11–7:04
假設已知 log 分析約四百毫秒,但不知道這四百毫秒去哪了。Chrome performance 面板記錄的不是單一函式,而是整段互動期間瀏覽器在做什麼。操作方式:打開 DevTools 的 Performance 面板,按角落的 record 按鈕(或快捷鍵),執行同一個搜尋(搜 discount),拿到結果後停止錄製,Chrome 會產生一份報告。

推理因為上一段判定需要一個 profiler,所以作者接著示範最容易取得的那一個——Chrome 內建的面板,不必安裝任何東西,延續了他一路以來「先用最便宜的工具」的取捨。關鍵的實驗設計是:他刻意重跑同一個搜尋(搜 discount),讓新工具量的和先前 baseline 量的是同一件事,這樣兩邊的數字才對得起來。
- CChrome Performance 面板:不必安裝的錄製式 profiler,記錄各執行緒所有工作的時間軸→ 畫地圖
- P錄一段互動:先設 CPU throttling 4x–6x,按錄(Cmd/Ctrl+E)、再做跟 baseline 同一個操作、停止→ 練習
- Edemo:Log Analyzer 吃 50,000 筆 log 花 473.9ms,可切 Main thread / Worker→ 存+演練
AI 補充畫面上受測的是 localhost:5173 的 Log Analyzer(頁面標著 Ingested 50,000 logs),而且它本身就內建「Where analysis runs:Main thread / Web Worker」的切換與 LAST RUN 473.9 ms——這正好對得上作者口中「大約四百毫秒」的量級,也說明這個 demo 從一開始就是為了做前後對照而設計的。DevTools 這裡停在 Performance 面板的 Local metrics 首頁(LCP 0.07s、CLS 0.00、INP 24ms),錄製鈕在面板左上角,Cmd/Ctrl+E 是開始與停止的快捷鍵。有兩件作者略過但會直接影響結論的事:其一,錄之前應該把 CPU throttling 調成 4x 或 6x,否則在開發機上量到的 long task 會比真實使用者樂觀得多,畫面右下角的 Environment settings 就是設定的地方;其二,順序必須是「先按錄、再互動」,否則那次互動根本不在時間軸上。
術語:Chrome DevTools Performance panelInteraction to Next Paint (INP)
7. 火焰圖指出:時間全花在 main thread 7:04–8:29
作者不重講每個面板,只聚焦一個想法:Performance API 答的是「花了多久」,Chrome performance 答的是「瀏覽器把時間花在哪」。這份錄製裡 main thread 上立刻看得到一個 long task,放大後大部分時間落在 simulateHeavyWork。真正的問題不是互動花了四百毫秒,而是這四百毫秒幾乎都在 main thread 跑 JavaScript。到這裡才有依據決定要改演算法、搬進 web worker、還是根本別做這件事——不再是猜測,而是根據測量做決定。


推理因為上一段拿到的是一份資訊量過大的時間軸,所以作者接著做的是「限縮問題」而不是「全面導覽」:他把兩個工具各自回答的問題並排——Performance API 答「花了多久」、Chrome Performance 答「時間花在哪」——順著這個問題一眼找到 main thread 上的 long task,放大後看見大部分時間落在 simulateHeavyWork。由此他改寫了問題的敘述:真正的麻煩不是互動花了四百毫秒,而是這四百毫秒幾乎都在 main thread 上跑 JavaScript。到這裡才有依據談改演算法、搬 web worker 或乾脆不做。
- Cmain thread 被佔住畫面就不更新;long task = 主執行緒連續超過 50ms 的工作→ 畫地圖
- Cflame chart:橫軸時間、縱軸呼叫深度、越往下是被誰呼叫;self time ≈ total time 才代表時間燒在這個函式自己→ 畫地圖
- ESummary:13.53s 錄製裡 Scripting 3,373ms、Rendering 205ms、Painting 139ms——沒放大就知道不必查樣式→ 存+演練
- R兩個工具各答一題:Performance API 答「花了多久」,Chrome Performance 答「時間花在哪」→ 存+回想
AI 補充兩張圖分別對應兩個判讀層次。全景那張的左下角 Summary 已經給了答案的輪廓:13.53 秒的錄製裡 Scripting 3,373ms、Rendering 205ms、Painting 139ms、System 264ms——scripting 一項就壓倒其他所有,還沒放大就知道不必去查樣式或繪製。放大之後(範圍 2.73s–3.75s)在「Main — http://localhost:5173」軌上疊出完整呼叫堆疊:Function call → run → Run console task →(anonymous)→ runMainThreadDemoAnalysis → runDemoAnalysis → simulateHeavyWork,tooltip 寫著 209.49 ms(self 193.36 ms),來源 src/lib/analysis.ts。這裡有一個作者沒講、但是「該不該改演算法」的判斷關鍵:self time 幾乎等於 total time,代表時間真的燒在這個函式自己的迴圈裡,而不是它呼叫的別人——如果 self 很小、total 很大,該優化的就是被它呼叫的下層而不是它。另外 Frames 軌上那格 39.5ms 說明畫面在這段期間確實掉了幀,這是使用者真正感覺到的東西。順帶一提,畫面上的 long task 是兩百毫秒級,而作者口中的四百毫秒是整次互動(含多次分析)的量級,兩個數字尺度不同,不必硬對在一起。
術語:flame chart
8. React Profiler:只量你在意的那棵子樹 8:29–9:52
前面把瀏覽器當整體看,但若想知道 React 本身在做什麼呢?也許昂貴的不是演算法,而是 React 渲染了遠比預期多的元件。React 提供 Profiler 元件:作者通常不 profile 整個 app,只把在意的部分(這裡是搜尋結果)包起來,並給一個 onRender callback。每次這棵子樹渲染完,React 就呼叫它。callback 給的值很多,他只看三個——id(在量哪一塊)、phase(初次渲染還是更新)、以及最常看的 actualDuration(React 花多少時間渲染這棵子樹)。示範中結果是約 30 毫秒。



推理因為上一段把嫌疑指向 main thread 上的 JavaScript、卻無法排除 React 渲染這條線,所以作者接著引入 React 內建的 Profiler 元件。他強調不 profile 整個 app,只把在意的那一塊(搜尋結果)包起來,並掛一個 onRender callback;每當這棵子樹渲染完,React 就呼叫它。callback 給的值不少,他只固定看三個:id(在量哪一塊)、phase(初次掛載還是更新)、以及最常看的 actualDuration(React 花多少時間渲染這棵子樹)。示範得到的數字約是 30 毫秒。
- CReact Profiler:<Profiler id onRender> 包住子樹,每次 commit 回報 actualDuration→ 畫地圖
- P只 profile 在意的那棵子樹:包 Profiler、掛 onRender、固定看 id / phase / actualDuration 三個值→ 練習
- RonRender 六個參數:id, phase, actualDuration, baseDuration, startTime, commitTime→ 存+回想
- RbaseDuration = 假設完全沒 memo 時的估計渲染時間;跟 actualDuration 一起看才知道該不該加 memo→ 存+回想
AI 補充程式碼截圖的註解把分工講得比旁白更清楚:Performance API 量的是 your logic,Profiler 量的是某一棵子樹的 React render / commit,「當瓶頸可能在 UI 而不是 analyzeLogs 時才用它」。寫法是 `<Profiler id='Results' onRender={onRender}><ResultsList logs={logs} /></Profiler>`,callback 簽章寫成 `onRender(id, phase, actualDuration, baseDuration)`。三點補充:其一,真實的 onRender 其實還有 startTime 與 commitTime 兩個參數,作者的範例只解構了前四個,需要把 React 的 commit 對到 Chrome 時間軸時就會用到後兩個。其二,畫面上出現、旁白卻沒提的 baseDuration 很值得看——它是「假設完全沒有 memo 化時的估計渲染時間」,和 actualDuration 一起看才知道 memo 到底有沒有省到;只看 actualDuration 很難回答「該不該加 memo」。其三,id 之所以是必填,是因為一個 app 可以放很多個 Profiler 而共用同一個 callback,沒有 id 就分不清這筆是誰。最後要留意,30 毫秒這個數字在影片裡是用字幕卡(React (30ms) Rendering)帶過去的,不是 console 印出來的實測畫面。
術語:React Profiler
9. 三個數字擺在一起才有結論 9:52–10:32
React 渲染 30 毫秒 vs 前面量到的 log 分析四百毫秒——對照之下立刻看出 React rendering 不是這裡最大的問題,時間還是花在 main thread 分析 log。這正是作者要把三個工具一起講的原因:只看 React profiler 會跑去優化元件;只看 Performance API 會知道慢但不知為何。合起來才有完整的圖像。
推理因為上一段拿到的數字單獨看不出好壞,所以作者接著做的其實只是一個除法:30 比 400 大約是 7%,於是結論立刻浮現——React rendering 不是這裡最大的問題,時間仍然花在 main thread 上分析 log。他順勢說明為什麼堅持要把三個工具一起講:只看 React Profiler 會跑去優化元件,只看 Performance API 會知道慢卻不知為何,合起來才有完整的圖像。
- Cbottleneck:佔掉最多時間的那段;三個數字並排才看得出來——30ms React ÷ 400ms 互動 ≈ 7%,React 不是問題→ 畫地圖
- EReact render 30ms vs 整次互動約 400ms:只看 Profiler 會去優化元件,只看總時間會冤枉 React→ 存+演練
- A每個工具都是拼圖的一塊,有一整片自己看不到的區域→ 批判類比
AI 補充這個對照能成立有一個沒被說出來的前提:兩個數字必須量的是同一次互動的同一段時間。如果那 30 毫秒來自另一次 render,或分析其實已經跑在背景執行緒上,兩者就不在同一條時間軸上,比例也就沒有意義——這也是為什麼作者前面要刻意重跑同一個搜尋。另外值得注意的是,工具的盲點是對稱的:若當初只看 React Profiler,看到 30 毫秒會滿意地說「React 很快,沒問題」,卻完全不知道使用者其實等了四百毫秒;反過來只看總時間,也可能冤枉了根本沒問題的 React。所謂「拼圖的一塊」不是修辭,而是說每個工具都有一整片自己看不到的區域。
術語:bottleneck
10. Profiler 的數字怎麼讀才對 10:32–11:20
作者補充一個前提:profiling 本身有 overhead,所以 React Profiler 在正式的 production build 是關掉的;development mode 的數字也偏吵,尤其開了 StrictMode。因此他把這些數字當作「A 實作對 B 實作」的相對比較用,而不是拿來當 production 的絕對值。他也提到 React 可以整合進 Chrome performance,在 timeline 上看到 React 專屬的時間資訊,那值得另開一支影片。
推理因為上一段的結論完全建立在數字的可信度上,所以作者接著不是繼續往下推,而是回頭替自己的結論設界線:他把這些數字定位成「A 實作對 B 實作的相對比較」,而不是可以拿去對外宣稱的 production 絕對值。這個限縮反而讓前一段的結論更站得住——30 比 400 這種數量級的差距,不會因為量測誤差而翻盤。
- RProfiler 數字只當 A 實作對 B 實作的相對比較,不是 production 絕對值→ 存+回想
- RStrictMode 開發模式把 render 跑兩次逼出不純副作用,actualDuration 偏高且不是穩定兩倍→ 存+回想
- RChrome 時間軸左側的 Scheduler ⚛ / Components ⚛ 軌就是 React 寫進瀏覽器時間軸的自訂軌道→ 存+回想
AI 補充StrictMode 為什麼吵值得說清楚:它在開發模式下會刻意把 render 函式跑兩次,用來逼出不純的副作用,因此 actualDuration 會系統性偏高,而且不是穩定的兩倍(第二次通常較快,快取已熱),所以連「除以二」都不可靠。production build 之所以關掉 Profiler,是因為每個 commit 都要記時間戳與累加,那個成本不該讓所有使用者付;真要在接近正式的環境測,得改用特別開啟 profiling 的 build。至於作者提到「React 也可以整合進 Chrome performance」——這件事其實在他自己的錄製畫面上就已經發生了:第 7 段那兩張時間軸截圖左側的 `Scheduler ⚛` 與 `Components ⚛` 兩條軌,就是 React 寫進瀏覽器時間軸的自訂軌道,它讓你在同一條時間軸上同時看到 React 的工作與底下的 JavaScript 呼叫堆疊,某種程度上就是把這一段和前面兩個工具接起來。
術語:profiling overhead
「React can also integrate with Chrome performance where you will see」
11. 總結:先量,再決定要優化什麼 11:20–13:12
快速複習:三個工具各答一個問題——Performance API 是操作花多久、Chrome performance 是瀏覽器把時間花在哪、React Profiler 是一次 React render 有多貴,誰也取代不了誰,各給拼圖的一塊。所以下次有人說「頁面感覺很慢」,不要直接跳進優化:先量、建立 baseline、搞清楚時間去哪,然後才決定改什麼。作者最後點出這支影片只是效能的「測量」這一半,找到瓶頸之後的取捨(web worker、快取、渲染策略、資料流、虛擬化、code splitting)是他課程與書的範圍,React Profiler 也值得單獨一支深入影片。

推理因為前面三個工具是被各自的不足一個接一個逼出來的,所以作者最後把三個問題排在一起做結——Performance API 是「這個操作花多久」、Chrome performance 是「瀏覽器把時間花在哪」、React Profiler 是「一次 React render 有多貴」——再回到開場那句「頁面感覺很慢」,給出行動守則:不要直接跳進優化,先量、建立 baseline、搞清楚時間去哪,然後才決定改什麼。最後他明確劃界:這支影片只做完測量這一半,找到瓶頸之後的取捨屬於他的課程與書。
- A三個工具是三種解析度:自己插的探針 → 瀏覽器全景錄影 → 框架的一次 commit;由粗到細用→ 批判類比
- R找到瓶頸後的下一半:worker、caching、rendering strategy、virtualization、code splitting→ 存+回想
AI 補充把三個工具看成三種「解析度」會比背誦三句話更好用:Performance API 是你自己插的探針,解析度等於你打點的粒度;Chrome Performance 是瀏覽器的全景錄影,解析度是每個 task 與呼叫堆疊;React Profiler 是框架層的一次 commit,解析度是一棵子樹一次渲染。使用順序就是解析度由粗到細——先用最便宜的確認「真的有問題、大概多大」,再花力氣錄時間軸找位置,最後才進到框架內部。畫面上的字卡是三個工具逐一出現的,這一張停在 Chrome Performance/Where the browser spends its time。另外值得留意作者劃界的方式:他點名的下一半題目——web worker(見第 1 段)、caching、rendering strategy、virtualization、code splitting(見第 1 段)——每一個都改變了系統結構,因此每一個都必須先有 baseline 才知道是賺是賠,正好把整支影片繞回開場的那個迴圈。
4. 總結
作者先拆解「這頁很慢」這句話:有多慢、哪裡慢、跟什麼比——沒有這三個答案,優化就只是猜測,所以要先有 baseline。第一把尺是 Performance API,因為它內建於瀏覽器、不綁框架,便宜到可以隨時重跑;用 performance.now() 夾住 analyzeLogs 就得到第一個總數。但總數看不出該改哪一步,於是換成 mark / measure 把 search、filter、aggregate 分別命名量測,用 console.table 列成一張表。表格指出 search 最慢,卻答不出那段時間是花在 JavaScript、layout 還是繪製——這是 Performance API 的天花板,也逼出第二把尺。Chrome Performance 錄的不是單一函式,而是整段互動期間瀏覽器做的每件事;火焰圖上 main thread 的 long task 把四百毫秒定位到 simulateHeavyWork,結論從「很慢」變成「幾乎全部花在 main thread 跑 JS」。但瀏覽器層的視角看不進框架內部,於是第三把尺 React Profiler 上場:包住在意的子樹、掛 onRender,只看 id、phase、actualDuration 三個值,量到 React 渲染約 30 毫秒。關鍵在於把 30 毫秒與先前的四百毫秒並排——React 不是瓶頸,直覺被數據推翻。最後作者補上讀數字的規矩(profiling 有 overhead、production build 關閉、StrictMode 會吵,所以只當相對比較用),收束成一句:三個工具各答一個問題,誰也取代不了誰;下次聽到「頁面很慢」,先量、建 baseline、看時間去哪,然後才決定改什麼。
勘誤總整理
確定錯誤/已過時見仁見智(取決於版本或情境)
| 段落 | 原話(transcript 逐字) | 說明 |
|---|---|---|
| 5. Performance API 的天花板:知道多久,不知道為何 5:58 |
「Is it layout or is it rendering? The performance API cannot answer these questions.」 | 作為「why 要靠 profiler」的教學對比,這句話成立;但講成 Performance API 完全回答不了 layout / rendering 的歸因,以今天的瀏覽器來說偏絕對。PerformanceObserver 的 long-animation-frame entry 會給出該影格的 styleAndLayoutDuration 與造成阻塞的腳本來源,layout-shift、event、element 等 entry type 也各自提供部分歸因。準確的說法是:Performance API 給得出「哪一類工作佔了多久」,但給不出呼叫堆疊,所以要定位到某一行程式碼仍然需要 profiler。 依據: Long Animation Frames API(Chrome 123 起預設開啟,2024);PerformanceObserver 支援的 layout-shift / event / element entry type(Event Timing、Layout Instability 規格)。 |
| 10. Profiler 的數字怎麼讀才對 10:58 |
「React can also integrate with Chrome performance where you will see」 | 這句在影片脈絡裡被講成一個通則,但它其實高度依賴版本。React 早年靠 User Timing marks 提供的 DevTools 時間軸資訊在 React 18 已被移除;現在畫面上看得到的 `Scheduler ⚛` 與 `Components ⚛` 自訂軌道,是 React 19.1 之後才加入的 Performance tracks 功能,而且只在開發模式與支援自訂軌的 Chrome 版本才會出現。用舊版 React 或 production build 的人照著做會看不到任何 React 軌道。 依據: React 19.1 release notes(2025)新增 Performance Tracks(Scheduler ⚛ / Components ⚛);React 18 移除 unstable_trace 與 User Timing 整合;Chrome DevTools extensibility API for custom tracks(Chrome 125+)。 |
5. 推薦三個下一步
1. 往下挖深:把火焰圖讀懂
第 7 段作者刻意跳過面板細節,只用「時間花在哪」收斂整份錄製。但 self time 與 total time 的差別、Scheduler / Components 這些 React 專屬軌怎麼讀,是把 Chrome Performance 從「看得出誰最寬」升級成「說得出為什麼」的關鍵缺口。
YouTube 搜尋:chrome devtools performance flame chart explained self time vs total time devtools chrome performance panel deep dive 2025
2. 往旁邊對照:實驗室數據 vs 真實使用者數據
全片量的都是自己機器上的一次操作(lab data),但第 1 段那句「跟什麼比」在生產環境的答案是真實使用者的分佈。Core Web Vitals 與 INP、以及 PerformanceObserver 蒐集 field data,正好補上這個對照面。
YouTube 搜尋:Core Web Vitals INP explained PerformanceObserver real user monitoring lab data vs field data web performance
3. 往上應用:量到瓶頸之後怎麼修
作者自己說這支只講了測量這一半,找到瓶頸後的取捨(web worker、快取、虛擬化、渲染策略)留給了課程。第 7 段那個卡住 main thread 的 long task,正是 web worker 與時間切片最典型的適用場景。
YouTube 搜尋:web worker offload main thread javascript long task breaking up main thread work react list virtualization performance
- 接著看 ←15 Frontend Concepts Every Senior Dev Has Mastered · 那站建立 main thread、CRP、lazy loading 的心智模型,這站教你怎麼量它們
- 接著看 ←Top 15 Frontend Interview Questions for 2026 (wa/ Senior Engineer) · 面試題點到 main thread 阻塞與 code splitting,這站給證明改動有沒有用的量法
- 接著看 →Frontend System Design Explained w/ Senior Engineer (Microfrontends, Monorepo, MCP UI, Reactjs) · 量出瓶頸之後,那站給 bundle 切分、渲染策略等可以拿來改的選項
- 接著看 →Real Frontend System Design (from a Senior Engineer) · 這站把 web worker 當成待驗證的假設,那站把它當成 scale level 的零件用
- 相關 —Senior Frontend Interview 2026 🎉 | Javascript 🎯 (Mock) [Most Asked Questions] · 共用 layout、virtualization、code splitting:一站口頭答題,一站實際量給你看
- 接著看 ←Core Web Vitals Explained: LCP, INP & CLS · 三個指標的成因懂了,接著學怎麼用 Performance API 與 Profiler 量它們
- 接著看 ←Vapor Mode is the Future of Vue · Vapor 只給了「更快更省」的承諾沒給數字,這站教你怎麼量出來