2026 年資深前端面試:15 題背後的同一套推理方式
文字解析 · 1 支影片 · 產生於 2026-10-01 00:54
章節(17)
1. Outline
- 起點 · Top 15 Frontend Interview Questions for 2026 (wa/ Senior Engineer)
從「為什麼答對了還是被判定不夠 senior」出發,用 15 題示範同一套動作:先重新框定問題、再建立分類架構、然後在架構裡填內容、最後把結論接回成本或風險的判準。三個尺度依序展開——AI 工作流、CSS 與 JavaScript 基礎、前端系統設計。
2. YouTuber 的思維推導
Top 15 Frontend Interview Questions for 2026 (wa/ Senior Engineer)
theSeniorDev · 50m40s · 字幕 en · vision=on (使用者指定 vision=true;且這支影片是白板教學型,作者大量說「這是我們的 padding box」「我把它改成藍色」「這是 V8 引擎」,說明高度依賴他當場畫的圖。) · YouTube
預估 vs 實際耗時
| 階段 | 預估 | 實際 |
|---|---|---|
| fetch | 6s | 2s |
| segment | 2m11s | 7m00s |
| shot | 1m44s | 10s |
| analyze | 21m15s | 25m00s |
| render | 5s | – |
作者的出發點不是「這 15 題的正確答案是什麼」,而是「為什麼答對了還是被判定不夠 senior」。他的答案是:資深不在於知道結論,而在於答案裡看得見一條可驗證的推理鏈。整支影片就是同一套動作重複 15 次——先把問題重新框定(是載入慢還是輸入卡?
是 client render 還是 SSR?),再建立一個分類架構(static/dynamic、testing pyramid、input/output token、三根支柱、A/B/C 計分),然後在架構裡填內容,最後把結論接回一個成本或風險的判準。而串起這 15 題的暗線是 AI:AI 會為了 demo 好看而硬寫像素、複製狀態、堆假的測試覆蓋率、產出巨大檔案,於是所有「老派基本功」——box model、specificity、event loop、記憶體管理、bundle 切分——都從考古題變成了「你有沒有能力接住 AI 產出的爛攤子」的檢查點。
他的結論因此是一個循環:越懂底層,越能給 AI 正確的邊界;邊界越好,AI 產出的東西越少需要你改;改得越少,token 與 GPU 成本越低。這條循環同時也是他對「前端會不會消失」的回答:不會,但留下來的位置會集中在能定義邊界的人——做 design system 的人,和懂系統設計的人。
推理鏈:每段留給下一段的線索
每一列是一段,箭頭後的字是這段留給下一段的線索。點段落標題跳到該段。
- 開場:被拒的理由是「不夠 senior」 → 如果資深的差別在於推理鏈,那第一題就要示範這條鏈長什麼樣:從一個 AI 寫出來的小 bug 出發,看能推到多深。
- Q1 hover 放大:reflow 還是 compositor → 這一題的推理停在「知道底層機制才知道 AI 的寫法貴在哪」。那麼問題就變成:面對整天在產生這種寫法的 AI,你的工作流要怎麼設計,才不用每次都事後救火?
- Q2 資深的 AI 工作流長什麼樣 → 工作流限制的是 AI 產出的方向,但沒有人能保證它每次都照做。所以下一個問題是:怎麼用自動化的方式,持續檢查產出的品質?
- Q3 用 AI 時怎麼守住程式碼品質 → static 那一側的答案已經完整了,但他自己也承認靜態檢查抓不到「檔案太大、邏輯全糊在一起」。這就把問題推到 dynamic 那一側:測試要怎麼寫,才不會變成另一種假象?
- Q4 測試:覆蓋率、金字塔、AAA → 他在推導裡兩次用「花多少 token 才找得到 bug」當作判準,等於已經把成本當成架構決策的變數。那接下來就該正面處理:token 花費本身該怎麼被設計進前端架構?
- Q5 怎麼把 token 花費壓低 → 兩個手段裡,micro-frontend 是有條件的,但「標準化」是無條件划算的。那就得先回答:一個能讓人與 AI 都少寫程式碼的 design system,到底該長什麼樣?
- Q6 什麼樣的 design system 算好 → design system 能保證顏色與間距一致,但保證不了版面在不同螢幕上不壞掉。要判斷版面為什麼壞,得先回到最底層:瀏覽器到底是怎麼決定一個元素佔多大?
- Q7 CSS box model 與 box-sizing → 現在知道單一元素怎麼算大小了,但實際頁面上同一個元素常常同時被好幾條規則指到。那麼下一個問題是:瀏覽器怎麼決定哪一條說了算?
- Q8 CSS specificity 怎麼算出勝負 → 現在單一元素的尺寸與樣式歸屬都清楚了,但這些規則放到不同螢幕上還要能活。所以問題升級成:一個版面要具備哪些條件,才會自己適應各種寬度?
- Q9 響應式設計的三根支柱 → 到這裡,CSS 這一側——尺寸、優先級、版面適應——已經連成一串了。但畫面卡頓的另一半原因不在 CSS,而在 JavaScript 什麼時候被執行。所以要換到執行模型:event loop。
- Q10 event loop 的完整運作 → 知道了工作是怎麼排隊的,下一步自然是問:當排隊的工作太重時,實際會長成什麼樣的線上問題?
- event loop 在生產環境的真實 bug → 他這個案例談的是輸入反應變慢。但真實情境裡使用者說「很慢」時,也可能指的是打開很慢。那就需要一套能先分辨、再對症下藥的流程。
- Q11 vibe coded 的 React 很慢怎麼辦 → 到這裡談的都是「怎麼讓現有的東西變快」。但如果問題不是慢,而是使用者數量整整多了一百倍呢?那就從最佳化變成了系統設計。
- Q12 object 與 map 的差別 → Map 可以用物件當 key,這帶出一個他還沒處理的後果:那些被當成 key 的物件,什麼時候才會被釋放?
- Q13 Map、WeakMap 與垃圾回收 → 記憶體與資料結構這一層談完了,前端基礎的部分也就收束了。剩下最後兩題是尺度最大的:當使用者變成一百倍時,整個前端要怎麼撐住?
- Q14 從一千到十萬日活怎麼擴 → 擴展的問題到這裡是關於「怎麼把東西送到使用者面前」。最後一題把方向反過來:資料要怎麼持續從伺服器流回使用者?
- Q15 LLM 聊天機器人用哪種即時協定
3. 逐段說明
Top 15 Frontend Interview Questions for 2026 (wa/ Senior Engineer)
1. 開場:被拒的理由是「不夠 senior」 0:00–0:47
主持人指出前端工程師現在最常收到的拒絕理由不是答錯,而是「你不夠 senior」。因為把面試題答對已經不夠,你必須在答案裡透露出資深的技術深度。整支影片要用 15 題常見前端面試題示範「資深的人會怎麼答」,涵蓋前端基礎、前端系統設計,以及 AI 輔助的前端工程。
推理作者從一個統計現象出發:前端工程師收到的拒絕理由,最常見的不是「答錯」而是「不夠 senior」。他把這件事翻譯成一個可操作的目標——答案本身不是產品,答案裡透露出的技術深度才是。因此整支影片的形式不是「15 個標準答案」,而是「同一題,junior 會怎麼答、senior 會怎麼答」的對照。
AI 補充作者沒有明說的是,「不夠 senior」在面試現場其實是一個很具體的判斷:面試官在聽你有沒有能力把一個開放問題**先切開再回答**。junior 的答案是一個結論(用 CDN、寫測試、用 memo),senior 的答案是一個結構加上取捨(在什麼條件下用、代價是什麼、不用會怎樣)。這也解釋了為什麼影片要把三個看似不相干的主題放在一起:前端基礎、系統設計、AI 輔助工程。它們是同一個能力在三個尺度上的展現——你能不能說出「為什麼」。
2. Q1 hover 放大:reflow 還是 compositor 0:47–3:30
第一題是 AI 寫的按鈕靠改 width 做 hover 放大。Bogdan 指出改 width 會觸發 reflow,瀏覽器必須重算這塊版面、重跑整條 rendering,佔住 main thread。資深的做法是改用 CSS transform scale 加 transition,讓動畫直接走 compositor thread 與 GPU,不動 layout tree。他進一步說:用 JavaScript 去改更糟,因為那還要經過 event loop 與 call stack,改了 DOM 再引發 reflow;原則是能繞過 main thread 就繞過,必要時把元素提升成獨立的圖層。
推理因為要示範推理鏈,所以作者刻意選了一個看起來只有一行答案的題目:AI 寫的按鈕靠改 width 做 hover 放大。他沒有直接說「改用 transform」,而是先問「改 width 會發生什麼」——會觸發 reflow,瀏覽器要重算這塊的 layout、重跑整條 render、再產生一次像素。有了這個代價,替代方案才有理由:CSS transform 的 scale 不動 layout tree,可以交給 compositor thread 在 GPU 上算完。最後他把同一條推理再往上推一層:如果改用 JavaScript 來改,成本更高,因為多了 event loop 與 call stack 這一段,改完 DOM 還是會回到 reflow。所以整段的結論不是「用 transform」,而是「能繞過 main thread 就繞過」。
- Creflow:改 width 這類幾何屬性會讓瀏覽器重算 layout、重跑整條 render;能繞過 main thread 就繞過→ 畫地圖
- R能繞過 layout 的只有可合成屬性(主要 transform、opacity);transition: width 照樣每影格 reflow→ 存+回想
AI 補充這裡要補一個作者略過的精確度問題:**不是所有 CSS transition 都會繞過 reflow**。繞過 layout 的只有少數「可合成」的屬性,實務上主要是 `transform` 與 `opacity`。如果你寫 `transition: width 0.2s`,每一影格照樣觸發 layout 與 paint,一點都不會比 JavaScript 改 width 便宜。真正的規則是**看你動的是哪個屬性**,而不是看你用的是 transition 還是 JavaScript。另外「promote 成獨立圖層」也不是免費的:每個 compositing layer 都要吃 GPU 記憶體,濫用 `will-change` 反而會讓行動裝置變慢,通常是短暫開啟、動畫結束就拿掉。
術語:layer promotion
「Because CSS transitions they bypass the reflow and they go straight to what we call the compositor thread.」
3. Q2 資深的 AI 工作流長什麼樣 3:30–7:38
第二題:除了「開 Claude Code」之外,你的 AI 前端工作流是什麼。Bogdan 說 junior 的答案就是「我用 Claude Code」,資深的答案要有結構:一、先用 plan mode,並在規劃時接上你的 design system 與 Figma MCP,讓 agent 用既有積木規劃;二、加上結構性約束,特別是 state 要遵守 single record principle、CSS 用相對單位,因為模型為了 demo 好看會過度貼合 prompt,重複 state、硬寫顏色與像素;三、programming to interfaces,盯緊 component 的 props 與 state 形狀。他還補上一條 code review 原則:全域設定、測試、linter、type checker、package.json 這類「爆炸半徑大」的改動才需要人工細看。

推理因為問題已經變成「怎麼從源頭限制」,所以作者把答案設計成三層遞進的約束,一層比一層抽象。第一層是**素材**:先用 plan mode 規劃,並讓 agent 透過 Figma MCP 與既有 design system 取材,讓它用你的積木而不是自己發明。第二層是**結構**:state 遵守 single record principle、CSS 用相對單位。他解釋了為什麼需要這一層——模型被最佳化成「一個 prompt 就給出漂亮 demo」,代價是硬寫顏色與像素、重複狀態。第三層是**邊界**:programming to interfaces,盯緊 component 的 props 與 state 形狀,因為邊界一旦定好,AI 執行得其實不錯。最後他補上人力該花在哪:只有全域設定、測試、linter、type checker、package.json 這種爆炸半徑大的改動才值得逐行看。
- P資深 AI 工作流三層約束:素材(plan mode、MCP 取材)、結構(單一狀態來源、相對單位)、邊界(先定 props 形狀)→ 練習
- Rsingle record principle:同一份事實只存一次,是資料庫正規化搬到前端 state;AI 逐段產碼特別容易違反→ 存+回想
- Rblast radius:只值得逐行 review 的檔案是全域設定、測試、linter、type checker、package.json→ 存+回想
AI 補充作者把 single record principle 講得像是一條 AI 專屬規則,其實它就是資料庫正規化裡「同一份事實只存一次」搬到前端 state。重要的是它為什麼在 AI 時代特別容易被違反:模型是一段一段地產生程式碼,每個元件在局部看起來都合理,於是同一筆資料被複製到三個地方,各自有各自的更新路徑——bug 不會在寫的時候出現,而是在其中一份忘了更新時出現。第三層的「programming to interfaces」在前端的具體形式,多數人會漏掉:它不是叫你寫一堆 TypeScript interface,而是**先決定 props 的形狀再讓 AI 填實作**。props 形狀決定了狀態放哪裡、誰負責更新、能不能重用,一旦形狀錯了,裡面的程式碼寫得再漂亮也救不回來。至於「只細看爆炸半徑大的檔案」這條,可以直接落成機制:把這些路徑列進 CODEOWNERS 或 review checklist,不要依賴自己每次記得。
4. Q3 用 AI 時怎麼守住程式碼品質 7:38–10:55
第三題問在 AI 產碼的情況下怎麼維持品質。Bogdan 先示範資深答題法:先建立分類再展開,不要劈頭就說「我們有 linter」。他把品質措施分成 static(不執行程式)與 dynamic(執行程式)兩類。dynamic 就是 unit / integration / end-to-end 測試;static 因為便宜又確定,能加就多加:先 TypeScript,再 ESLint 或 Oxlint,最後是超出 lint 的靜態分析——cyclomatic complexity 與 code duplication 分析,因為 AI 只做局部最佳化,很會把同一段邏輯複製到很多地方。他也提醒看檔案長度:AI 常產出把邏輯與子元件全塞在一起的巨大檔案,靜態檢查會過,但一要寫 unit test 就得先拆。


推理因為問題是「持續檢查」,所以作者先示範他心目中資深答題的第一動作:不要一開口就報工具名,先建立分類。他把品質措施切成 static(不執行程式就能檢查)與 dynamic(要跑起來才知道)兩類。分完之後,投資順序就自己浮現了:static 便宜、快、結果確定,能加就多加。他因此給出遞進的三層——先 TypeScript 建立型別邊界,再 linter(ESLint 或更快的 Oxlint),最後是超出 lint 的靜態分析:cyclomatic complexity 與 code duplication。第三層之所以是新需求,理由直接接回上一段:AI 只做局部最佳化,會把同一段邏輯複製到很多地方。最後他點出這套檢查的盲點——檔案再大再肥,靜態檢查一樣會過,所以還要看檔案長度,而「能不能寫 unit test」正好是逼你拆檔案的力量。
- Cstatic analysis:不執行程式就能檢查,便宜、快、結果確定,能加就先加滿→ 畫地圖
- Estatic 檢查成本固定(CI 幾十秒),dynamic 隨程式碼量線性成長;AI 日產數百行時後者會失控→ 存+演練
AI 補充這裡值得補上作者沒明說的成本結構:static 檢查之所以該優先,是因為它的成本是**固定的**(跑一次 CI 幾十秒),而 dynamic 檢查的成本會隨程式碼量線性成長(測試要寫、要維護、跑得越來越久)。在 AI 每天產出數百行的情況下,任何隨程式碼量成長的成本都會失控,所以先把能固定成本的關卡設滿,是理性的順序。另外「code duplication 分析」在實務上要小心設定:AI 產生的重複常常是**結構相同但字面不同**(變數名不一樣、順序調過),純字串比對的工具會漏掉,要選有 token 級或 AST 級比對的工具才抓得到。他提到的 NPM 工具名稱在自動字幕裡是「follow」,這多半是聽寫錯誤——這個領域比較通行的是 jscpd(重複度)與 ESLint 的 complexity 規則。
術語:code duplication analysis
5. Q4 測試:覆蓋率、金字塔、AAA 10:55–13:50
第四題問在 AI 產出大量程式碼的前提下怎麼測試。除了說出三種測試,資深要再深一層:一、看 test coverage,目標 65% 以上但別衝過 85%,因為 AI 什麼都能寫測試,過高會變成沒驗證過的假覆蓋率;二、畫出 testing pyramid 並定位自己在哪裡,反駁「有 end-to-end 就好」的謬誤——e2e 掛掉只知道壞了、不知道壞在哪,除錯要燒很多時間與 token,而 unit test 能一段一段排除嫌疑;三、用 AAA(arrange / act / assert)統一測試結構,也讓 AI 照這個格式寫,人才讀得下去。


推理因為「寫測試」這個答案太淺,作者刻意再往下鑽三層。第一層是**量**:test coverage 該落在 65% 以上、但不要衝過 85%。他給的理由才是重點——AI 幾乎什麼都能寫測試,過高的數字往往是沒人驗證過的假覆蓋率。第二層是**形狀**:畫出 testing pyramid,並針對「有 e2e 就夠了」的流行說法給出反駁。他的反駁不是訴諸原則,而是算除錯成本:e2e 掛掉只告訴你系統壞了,不告訴你壞在哪,於是你得用時間或 token 去搜;有 unit test 時可以一段一段排除嫌疑,快也便宜。第三層是**結構**:用 AAA(arrange / act / assert)統一測試的骨架,並且直接把這個格式交代給 AI,讓產出的測試人讀得下去。
- Ctesting pyramid:unit 多、e2e 少;判準是訊號定位精度 ÷ 維護成本→ 畫地圖
- Ecoverage 落在 65% 以上但別衝過 85%:AI 什麼都能寫測試,過高多半是沒人驗證的假覆蓋率→ 存+演練
- Re2e 掛掉只說系統壞了不說壞在哪;unit test 可以一段段排除嫌疑,除錯便宜→ 存+回想
- P把 AAA(arrange / act / assert)格式直接交代給 AI,產出的測試人才讀得下去→ 練習
AI 補充作者對覆蓋率上限的說法值得再精確一點:85% 這個數字不是普世常數,重點在於**覆蓋率量的是「這行有被執行到」,不是「這個行為有被驗證」**。AI 很容易寫出跑過所有分支卻沒有任何有意義斷言的測試,覆蓋率報表照樣漂亮。所以真正該搭配看的是斷言的品質,或用 mutation testing(故意改壞程式碼,看有沒有測試因此失敗)來驗證測試本身有沒有效。至於 testing pyramid 的取捨,他推導出的其實是一條可以量化的判準:**測試的價值 ≈ 訊號的定位精度 ÷ 維護成本**。unit test 定位精度高、維護成本低但涵蓋範圍窄;e2e 反過來。「只寫 e2e」在 AI 時代誘人的原因也很實際——e2e 描述的是使用者行為,最好交給 AI 生成;但它同時也是最貴的除錯入口,這正是他要擋下來的地方。
術語:AAA patterntest double
6. Q5 怎麼把 token 花費壓低 13:50–16:52
第五題問怎麼降低前端的 token 花費。Bogdan 先把成本拆成 input 與 output,input 便宜得多,所以真正的槓桿是「做一個功能需要改動的程式碼量」。這正好回到傳統軟體工程的老目標:用最少的改動交付功能,也就是模組化與解耦。具體兩招:一、標準化——用 atomic CSS、design tokens、可重用元件,新功能幾乎不用寫 CSS;二、架構——人多的時候把 monolith 拆成 micro-frontends,餵給模型的只有那一塊。他還預言計價方式會從 token 轉成 GPU 運算時間,但結論不變:一樣是回到解耦、hexagonal architecture、SOLID。

推理因為成本已經是判準,作者先把它拆開:輸入與輸出是兩種價格,input 明顯便宜。拆完之後結論就很清楚——真正的槓桿不在讓模型少講話,而在**做一個功能需要改動的程式碼量**。這一步是整段的樞紐,因為它把新問題翻譯成了老問題:用最少的改動交付功能,正是傳統軟體工程一直在追求的模組化與解耦。於是他的兩個具體手段都不新:一是標準化,用 atomic CSS、design tokens 與可重用元件,讓新功能幾乎不需要寫新的樣式;二是架構,人多的時候拆成 micro-frontend,讓餵進去與改動的都只是其中一塊。最後他做了一個預測來檢驗自己的結論:計價方式遲早會從 token 換成 GPU 時間;而換了之後結論不變——依然是回到解耦、hexagonal architecture、SOLID。這個「換了單位還成立」的檢驗,正是他想示範的資深思考。
AI 補充作者說 input 比 output 便宜是對的,但少了一個對前端架構更有解釋力的因素:**prompt caching**。重複送出的前綴(專案規則、型別定義、常用檔案)可以被快取,命中時價格再降一個級距。這讓「穩定的東西放前面、變動的東西放後面」變成一個真的架構考量,也讓「檔案結構穩定」本身有了金錢價值。另外 micro-frontend 這個建議必須連著代價一起講,否則在面試裡反而扣分:它會帶來重複的執行時期相依、跨團隊的版本協調、以及整體 bundle 變大。他自己在後面談擴展性時也會鬆口說單體前端撐得住——所以正確的判準不是流量,而是**同時改同一份程式碼的人數**。
7. Q6 什麼樣的 design system 算好 16:52–20:12
第六題問好的 design system 長什麼樣,Bogdan 給出四個步驟:一、定義 design tokens,在現代 CSS 用 custom properties 存在 root,換品牌色只要改一行;二、建可重用元件,想要開箱即用的無障礙就用 Radix 這類 headless 元件再套自己的 token,不要重造輪子;三、套 composition patterns,把 button、input 組成可直接用的 form,讓人與 LLM 可以自助地選擇要用 token、用元件、還是用組合好的整塊;四、工具,Storybook 部署出來讓大家看得到,再加一個 design-to-code MCP 讓 Figma 成為 agent 的最佳化標的。他順帶回應「前端要消失了」的說法:留在前端的兩條路是走全端或做 design system。

推理作者把 design system 拆成由抽象到具體的四層,而且每一層都給出「換需求時要改幾個地方」的驗證。第一層 design tokens:用 CSS custom properties 存在 root,換品牌色是一行的事。第二層可重用元件:想要開箱即用的無障礙就用 Radix 這類 headless 元件再套自己的 token,因為 dropdown 這種東西自己做無障礙的成本高得不合理。第三層 composition patterns:把 button 與 input 組成 form,讓使用者可以自助地選擇要用 token、用元件、還是用組合好的整塊。第四層工具:Storybook 讓元件看得到、摸得到,再加一個 design-to-code MCP,讓 Figma 成為 agent 的最佳化標的——這正好接回第二段提過的 MCP。最後他用這個結構回應「前端會不會消失」:留下來的兩條路是往全端走,或往定義系統的人走。
- Cdesign tokens:把顏色、間距、字級抽成具名變數,是 design system 四層(token→元件→組合→工具)的底層→ 畫地圖
- EFigma 與 CSS 兩份 token 靠人工同步遲早漂移;要讓一邊成唯一來源,用工具生成另一邊→ 存+演練
AI 補充這四層真正的意義是**自助的層級**,作者講到了但沒點破:使用者可以按需求選擇進入的高度,用 token、用元件、或用組合好的區塊,三種都是合法用法。這正是它對 AI 特別有效的原因——模型最缺的就是「該從哪一層開始」的判斷,而一個分層清楚的系統把這個判斷變成了選擇題。另外有一個他略過的落差:design tokens 存在 Figma 與存在 CSS 是兩份資料,只要是人工同步,遲早會漂移。所以第四層的重點與其說是「接上 MCP」,不如說是**讓其中一邊成為唯一來源**,用工具把另一邊生成出來(例如用 Style Dictionary 之類的工具把 token 產成 CSS 變數)。沒有這一步,設計與程式碼的一致性仍然靠人的自律。
術語:headless components
8. Q7 CSS box model 與 box-sizing 20:12–24:40
第七題回到基本功:CSS box model。頁面就是一堆巢狀的盒子,每個元素由外而內是 margin box、border box、padding box、content box,outline 與 box shadow 畫在 border 之後、margin 之前。主持人補充為什麼 2026 年還要問這題:懂 box model 才修得動 AI 產生的版面 bug,而且很多十年經驗的人根本說不清楚,所以反而好突出。接著是這題的真正陷阱:box-sizing。預設是 content-box(extrinsic width 指的是內容的寬),border-box 才是把 padding 與 border 算進去。AI 很愛硬寫 width 300px,於是桌機好看、手機爆掉,再用一堆 breakpoint 補洞。最後他點名 margin collapsing 是進階題的常客。


推理作者先建立整體圖像:頁面就是一堆巢狀的盒子,每個元素由外而內是 margin box、border box、padding box、content box,outline 與 box shadow 畫在 border 之後、margin 之前。接著主持人插進一個關鍵的追問——2026 年了為什麼還考這個?答案有兩層:懂 box model 才修得動 AI 產生的版面 bug,而且很多十年經驗的人講不清楚,所以這題反而是拉開差距的地方。有了這個動機,作者才把重點推到真正的陷阱:box-sizing。預設的 content-box 表示你指定的 width 只算內容,padding 與 border 是往外加的;border-box 才是把它們算進去。他接著把這個機制連回 AI:模型很愛硬寫 `width: 300px`,於是桌機好看、手機爆掉,再用一堆 breakpoint 補洞——這正是「用 AI 前面 80% 很快、後面 20% 卡死」的典型現場。最後他點名 margin collapsing 是進階題的常客。
- Cbox-sizing 決定 width 這個數字算到哪一層:content-box 只算內容、border-box 含 padding 與 border→ 畫地圖
- Rbox model 由外而內 margin、border、padding、content;outline 在 border 外→ 存+回想
- Rmargin collapsing:垂直相鄰 margin 取較大者不相加;父子間沒 padding / border / BFC 時也會→ 存+回想
- P全域 border-box reset,並把 AI 硬寫的 width: 300px 換成相對尺寸,別用 breakpoint 補洞→ 練習
AI 補充自動字幕在這裡把 extrinsic 與 intrinsic 講反了,值得把正確的版本說清楚,因為這是這題最容易被問倒的地方。**extrinsic sizing** 是「由外部指定尺寸」,也就是你寫 `width: 300px` 的情況;**intrinsic sizing** 是「由內容決定尺寸」,例如 `width: max-content` 或根本不寫 width 的 block 元素。瀏覽器的預設不是 extrinsic,而是「寬度撐滿可用空間、高度由內容決定」。而 box-sizing 只在你指定了尺寸時才有意義:它決定那個數字算到哪一層為止。實務上幾乎所有專案都會寫 `*, *::before, *::after { box-sizing: border-box }`,因為 border-box 才符合人的直覺——說 300 就是佔 300。至於他推薦去查的 margin collapsing:相鄰的上下 margin 會合併成較大的那一個,而不是相加;它是早期為了排版文章段落而設計的例外,也是「明明加了 margin 卻沒有變化」這類 bug 的常見來源,而且只發生在垂直方向、且父子之間沒有 padding、border 或形成 BFC 時。
「However, the default of the browser is extrinsic width.」
9. Q8 CSS specificity 怎麼算出勝負 24:40–28:22
第八題問 CSS specificity。Bogdan 用一個具體例子:一邊是 form.big-form 設白底,一邊是 #my-form 設藍底,同一個元素同時符合。瀏覽器收集所有規則後跑 cascade algorithm,在同一 layer 內比 specificity,用 A、B、C 三層計分:ID 加 A、class 或屬性加 B、元素型別加 C。所以 form.big-form 是 0-1-1,#my-form 是 1-0-0。關鍵是這不是加總而是逐位比大小,先看 A,A 分出勝負就不看後面,所以 ID 那條贏,背景是藍的。他最後說可以在 Chrome DevTools 的 computed styles 看到被劃掉的那條,hover 上去還會顯示它的 specificity。


推理作者沒有直接背規則,而是先造一個最小的衝突情境:一邊是 `form .big-form` 設白底,一邊是 `#my-form` 設藍底,而 HTML 上的那個 form 同時符合兩者。有了具體衝突,機制才有登場的理由:瀏覽器收齊所有規則後跑 cascade algorithm,在同一 layer 內用 specificity 決勝,計分分成 A、B、C 三欄——ID 加 A,class 或屬性加 B,元素型別加 C。於是 `form .big-form` 是 0-1-1,`#my-form` 是 1-0-0。他接著強調最關鍵、也最常被答錯的一點:這**不是加總**,而是從左往右逐位比大小,前一位分出勝負就不看後面。所以 ID 那條贏,背景是藍的。最後他把答案落回可驗證的地方——在 Chrome DevTools 的 computed styles 看得到被劃掉的規則,hover 上去還會顯示它的 specificity。
- CCSS specificity:A-B-C 三欄(ID / class 屬性 / 元素)從左往右逐位比大小,不是加總→ 畫地圖
- Rform .big-form 是 0-1-1,#my-form 是 1-0-0;ID 那欄先分勝負所以藍底贏→ 存+回想
- Rcascade 順序:importance → layers → specificity → 出現順序;:where() 算 0 分→ 存+回想
AI 補充作者刻意跳過了 cascade 的其他步驟,但面試裡常常追問,值得補上完整的優先順序:**origin 與 importance → cascade layers → specificity → 出現順序**。specificity 只是其中一關,而且是相對靠後的一關。這解釋了兩個常見困惑:為什麼 `!important` 可以贏過任何選擇器(它在更前面的關卡就分勝負了),以及為什麼在 `@layer` 生效時,一個低 specificity 的規則反而可能贏——因為 layer 的順序比 specificity 先比。另外現代選擇器提供了刻意操控分數的手段:`:where()` 內的內容一律算 0 分,`:is()` 則取括號內最高的那一個。這讓「寫一條容易被覆寫的預設樣式」變成可以設計的事,也是設計系統很依賴的技巧。
10. Q9 響應式設計的三根支柱 28:22–30:35
第九題問響應式設計的核心原則,Bogdan 強調這要在設計階段就先定好,不然 AI 產出的東西在你的螢幕上很美、在手機上打不開。三根支柱:一、用相對單位而不是 px——rem 相對於 root、em 相對於父層,做可重用元件用 em,要跟整個 app 的尺度一致就用 rem;二、選對 layout 演算法——block 與 flex 對響應式最友善,grid 只有在你很熟或願意配大量 media query 時才划算;三、breakpoints 與 media queries——直接借 Tailwind 已經測過的那組,或叫 agent 全域套用 Tailwind 的 breakpoint,不要自己重造。


推理作者先給出一個時機上的主張:響應式要在設計階段就決定,不然 AI 產出的東西在你的螢幕上很美、在手機上打不開。這一句把責任從「事後 debug」移到「事前約束」,跟第二段的工作流是同一個邏輯。接著他把原則收斂成三根支柱。第一根是相對單位:不要用 px,改用百分比、rem 或 em;他還分辨了兩者——rem 相對於 root,em 相對於父層,所以做可重用元件用 em、要跟整個應用的尺度一致就用 rem。第二根是選對 layout 演算法:block 與 flex 對響應式最友善,grid 只有在你很熟、或願意配大量 media query 時才划算。第三根是 breakpoint 與 media query:直接借 Tailwind 已經被大量驗證過的那組,或叫 agent 全域套用,不要自己重造。
- Rrem 相對 root、em 相對父層;可重用元件用 em、要跟全站尺度一致用 rem;border 用 px 反而對→ 存+回想
- R影片少講 container queries:media query 問視窗多寬,元件真正在意的是容器多寬→ 存+回想
AI 補充「永遠不要用 px」講得太滿,值得修正成一條更精確的規則:**會隨使用者設定或容器改變的東西才用相對單位**——字級、行高、間距、元素寬度。而不該隨字級縮放的東西用 px 反而正確,最典型的是 border 寬度(1px 的邊框放大成 1.2px 只會變糊)與某些圖示尺寸。真正該避免的其實是**用 px 寫死那些應該跟著容器或字級走的尺寸**。另外他這套支柱少了現在最關鍵的一項:**container queries**。media query 問的是「視窗多寬」,但可重用元件真正在意的是「我被放進來的這個容器多寬」——同一張卡片在側欄與主欄要有不同版面,用 media query 是做不到的,因為視窗寬度一樣。`@container` 現在主流瀏覽器都支援了,它讓元件真正做到自我響應,這也剛好呼應上一段 design system 的目標。至於他說 grid 需要大量 media query,這其實是個過時的印象:`repeat(auto-fit, minmax(...))` 這類寫法可以完全不用 breakpoint 就自動換行。
「So, you never want to use pixels, for example.」
11. Q10 event loop 的完整運作 30:35–34:45
第十題要 event loop 的高層全貌。JavaScript 之所以單執行緒,是因為 DOM 是唯一的 singleton,多執行緒改 DOM 會產生無法預測的平行化 bug。所以有一條 call stack 負責執行,加上兩個佇列:promise 的 then 進 microtask queue,瀏覽器事件與 timer 的 callback 進 macrotask queue。這些結構住在 V8 引擎裡,而 loop 本身是瀏覽器用 C++ 實作的。迴圈的節奏是:main thread 空閒就執行 stack,清空後取一個 microtask,接著先讓 main thread 做 rendering,再回頭取下一個。他用「兩條輸送帶共用同一個機器人」比喻 JavaScript 與 rendering 共搶 CPU,這就是「不要阻塞 main thread」的真正意思,也是兩者不能同時跑的原因:邊 render 邊改 DOM 會讓 UI 狀態壞掉。



推理作者從一個因果問題開始:JavaScript 為什麼是單執行緒?答案是 DOM 是唯一的共享結構,多執行緒同時改它會產生無法預測的平行化 bug——瀏覽器其實是多執行緒的,是刻意不讓 JavaScript 多執行緒。有了這個前提,剩下的結構就都有理由存在:一條 call stack 負責執行,加上兩個佇列讓事情能非同步排隊——promise 的 then 進 microtask queue,瀏覽器事件與 timer 的 callback 進 macrotask queue。他還補了一個常被忽略的邊界:這些結構住在 V8 引擎裡,而 loop 本身是瀏覽器用 C++ 實作的,兩者不是同一個東西。最後他把整個節奏收成一個比喻——JavaScript 與 rendering 是兩條輸送帶共用同一個機器人,所以「不要阻塞 main thread」不是一句口號,而是這個結構的直接後果。
- Cevent loop:JS 單執行緒是因為 DOM 是唯一共享結構;一條 call stack 加 microtask / macrotask 兩個佇列→ 畫地圖
- AJavaScript 與 rendering 是兩條輸送帶共用同一個機器人,所以「別阻塞 main thread」是結構後果→ 批判類比
- Emicrotask queue 是一次清空不是一次一個:不斷產生 microtask 的 promise 迴圈會讓畫面凍住,setTimeout 版反而能更新→ 存+演練
AI 補充他描述的迴圈節奏有一個地方要修正,而且這正是面試最愛追問的細節:**microtask queue 不是一次取一個,而是一次清空。** 正確的節奏是:執行一個 macrotask(例如一個 click handler)→ 把 microtask queue 整個排乾,包含執行過程中新產生的 microtask →(必要時)rendering → 再取下一個 macrotask。這個差別有實際後果:一個不斷產生新 microtask 的 promise 迴圈會讓瀏覽器**永遠回不到 rendering**,畫面完全凍住;而同樣邏輯用 setTimeout 寫成 macrotask,畫面反而還能更新。「應該用哪一種排隊」因此是有答案的,不是風格問題。另外還有一層他沒提但很值得知道的:rendering 並不是每次迴圈都做,瀏覽器會對齊螢幕更新頻率,約每 16.7 毫秒一次,這就是 `requestAnimationFrame` 存在的位置——它保證你的程式碼在下一次繪製之前執行,這也是為什麼用它做動畫比 setTimeout 準。
「And we don't immediately pick the microtask.」
12. event loop 在生產環境的真實 bug 34:45–35:45
主持人追問理論之外有沒有真的遇過 event loop 的線上 bug。Bogdan 說最常見的就是推進 stack 的那份工作太重,而且多半出在元件框架:表單上掛了太多 event handler,使用者一打字就觸發一堆 re-render,畫面看起來卡卡的。解法是把 stack 當成「擅長做零碎工作」的東西,只餵它很小的獨立單位,並在大型元件框架裡盡量用 memoization 讓 re-render 變便宜。
推理作者給的案例把前一段的抽象結構落回具體現場:使用者在表單裡打字時畫面卡卡的。他的診斷直接對應到那個結構——問題出在推進 stack 的那份工作太重,而且多半來自元件框架:掛了太多 event handler,每次輸入都觸發一連串 re-render,全部擠在同一條 stack 上。解法因此也是從結構推出來的:stack 擅長的是零碎工作,所以要餵它很小的獨立單位,並用 memoization 讓每次 re-render 變便宜。
AI 補充這個診斷是對的,但少了一個更常見也更容易修的根因:**受控輸入把每一個按鍵都變成一次全樹更新**。每打一個字就 setState 一次,如果那個 state 位置太高,整棵子樹都要重新渲染。除了 memoization,實務上更有效的三種手段是:把輸入的 state 下推到最靠近的元件、對昂貴的後續動作(搜尋、驗證)做 debounce、以及在 React 18 之後用 `useDeferredValue` 或 `startTransition` 把非緊急的更新降級,讓打字本身永遠優先。另外「打字卡頓」還有一個容易被忽略的來源:卡的往往不是 JavaScript 本身,而是它引發的 layout——這剛好接回第二段的 reflow。用 DevTools 的 Performance 錄一段,就能分辨到底是 scripting 還是 rendering 佔掉了時間。
13. Q11 vibe coded 的 React 很慢怎麼辦 35:45–38:53
第十一題給了一個情境:PM 抱怨一個 vibe coded 的 React 專案很慢。Bogdan 先澄清是載入慢還是輸入反應慢,通常是載入慢。載入慢的路徑:先用 bundle analyzer 看 bundle size,砍掉或延後不必要的套件,用 dynamic import 與 code splitting 依路徑或使用者行為切,再考慮 SSR 與 server components 讓送到瀏覽器的 JavaScript 更少——因為 critical rendering path 上那些大檔案都要下載、解析、直譯完才輪到渲染。輸入卡頓的路徑則全在 re-render:要嘛避免(React.memo 比較 props),要嘛讓它更快(useCallback、useMemo);再加兩條少被提的——把不需要 state 的邏輯搬出元件、以及不要無謂地把 state 往上提,state 放在越接近使用的地方越好。
推理作者的第一動作不是給方案而是**先分辨**:是載入慢還是輸入反應慢?他判斷多半是載入慢,於是先走這條路。載入慢的推理鏈很直接:慢的原因是送出去的 JavaScript 太多,而 critical rendering path 上的大檔案都要下載、解析、直譯完才輪到渲染。所以行動順序是——先用 bundle analyzer 看清楚組成,砍掉或延後不必要的相依,用 dynamic import 依路徑或使用者行為做 code splitting,再考慮 SSR 與 server components 從源頭少送 JavaScript。另一條路是輸入反應慢,全部收斂到 re-render:要嘛避免(React.memo 比較 props,父層重繪時不跟著重繪),要嘛讓它更快(useCallback、useMemo 避免每次重建函式與值)。他還加了兩條比較少人提的:把不需要 state 的邏輯搬到元件外面,避免每次渲染都重建;以及不要無謂地把 state 往上提,因為 state 越高,它一變動要重繪的子樹就越大。
AI 補充他的順序是對的,但可以再加一條更前面的動作:**先量,再改**。用 Lighthouse 或 web-vitals 看是 LCP(載入)還是 INP(互動)差,數字會直接告訴你走哪條路,不必猜。這也讓「PM 說很慢」變成可以驗收的目標。另外 memoization 那一段有個容易踩空的地方:`React.memo` 做的是 props 的淺層比較,只要父層每次渲染都傳一個新建立的物件或函式進去,它就永遠比不相等,包了等於沒包——所以 `React.memo` 幾乎必須和 `useCallback`/`useMemo` 一起用才有效。這也是 React Compiler 的價值所在:它在編譯期自動處理這些包裝,讓這一整類手動最佳化變成不必要。最後補一個他略過但影響很大的載入因素:字型與圖片。JavaScript 只是 critical rendering path 的一部分,未預載的字型與未指定尺寸的圖片經常才是 LCP 與版面跳動的主因。
術語:lifting state up
14. Q12 object 與 map 的差別 38:53–39:56
第十二題回到 JavaScript 基礎:object 與 Map 的差別。最大的一點是 object 的 key 只能是字串或 symbol,Map 的 key 幾乎可以是任何東西,所以 Map 很適合拿來當「用任意值當索引」的記憶體空間。另外 object 要迭代得先過 Object.entries 或 keys,Map 本身就帶 iterator。但實務上大家還是用 object 居多,因為語法更順手;只有當你的 key 真的需要超出字串與 symbol 時才換 Map。
推理作者從最實質的差別切入:object 的 key 只能是字串或 symbol,Map 的 key 可以是任何值,包括物件本身。因為這一點,Map 適合當「用任意值當索引」的儲存空間。他補了第二個差別:object 要迭代得先過 `Object.entries` 或 `keys`,Map 本身就是可迭代的。但他接著給了一個很誠實的實務判斷——大家還是多用 object,因為語法更順手;只有當 key 真的需要超出字串與 symbol 時才換 Map。
AI 補充還有兩個差別在面試裡常被追問,值得補上。第一是**順序保證**:Map 嚴格保留插入順序,而 object 的整數型 key 會被排到前面並升冪排列——所以用 object 存 id 對應時,取出的順序可能跟你放進去的不一樣,這是很隱蔽的 bug 來源。第二是**乾淨程度**:object 會繼承 `Object.prototype` 上的東西,所以 `'toString' in obj` 是 true,Map 沒有這個問題(要避開的話得用 `Object.create(null)`)。另外效能上,頻繁新增刪除 key 的場景 Map 通常明顯較快,因為引擎對 object 的最佳化假設是形狀穩定。至於 JSON 序列化,object 直接可用而 Map 不行,這常常才是實務上真正的決定因素。
15. Q13 Map、WeakMap 與垃圾回收 39:56–42:10
第十三題把 Map 的話題接到記憶體管理。Map 裡的 key 不會被垃圾回收:即使那個物件在程式其他地方已經沒人參照,Map 仍然抓著它,一直塞就會漏記憶體。所謂「沒被用到」在記憶體管理上的意思是「從根部不可達」——物件要靠掛在 DOM 或 event handler 上才活著,沒掛住的就會被標記回收。WeakMap 的差別就在這裡:key 一旦不可達就會被移走,而 WeakMap 本身還留在記憶體裡。代價是 WeakMap 沒有 iterator,你沒辦法遍歷它,必須自己留著 key,所以寫起來比較麻煩;但拿來做函式的 memoization 時,它比純 Map 安全得多。
推理作者先講後果再講機制:Map 裡的 key 不會被回收,即使那個物件在程式其他地方已經沒人用了,Map 還抓著它,一直塞就漏記憶體。接著他補上「沒被用到」在記憶體管理裡的精確意思——不是「沒人呼叫」,而是**從根部不可達**;物件要靠掛在 DOM 或 event handler 這類還活著的東西上才留得下來,掛不住的就會被標記並回收。有了這個定義,WeakMap 的差別就清楚了:它對 key 的參照不算數,key 一旦不可達就連同那筆值一起被移走,而 WeakMap 本身還在。代價是它沒有 iterator,你不能遍歷它,必須自己留著 key——這一點正好呼應上一段的迭代器協定。最後他給出使用時機:拿來替函式做快取時,WeakMap 比 Map 安全得多。
- Creachability:從根部能否經參照鏈走到,是 GC 唯一判準;Map 持強參照所以 key 活著,WeakMap 的參照不算→ 畫地圖
- EWeakMap 沒 size、沒 iterator、key 必須是物件或 symbol——因為暴露這些會讓 GC 時機變成可觀察行為→ 存+演練
AI 補充他的結論對,但機制的說法要修正:**不是垃圾回收器「進不去」Map**,而是 Map 對它的 key 持有強參照。GC 完全看得見 Map 裡的東西,正因為看得見、而且那是一條從根部出發的有效參照鏈,所以它判定那個物件還活著。WeakMap 的差別在於它的參照被明確定義為弱參照,計算可達性時不算數。這個修正很重要,因為它解釋了為什麼 `weakMap.set(key, valueThatReferencesKey)` 這種寫法仍然會漏——問題從來不在容器,而在參照關係。補充兩件實務上的事:WeakMap 的 key 必須是物件或 symbol,不能是字串或數字(因為原始值沒有身分可以判斷可達性);還有它沒有 `size`,因為那個數字隨時可能因為 GC 而改變,暴露出來會讓 GC 的時機變成可觀察的行為。至於作者說的 memoization 用途——用 WeakMap 以物件參數當 key 快取結果,物件一被丟棄,快取自動消失,不需要手動清理,這確實是它最漂亮的應用。
術語:memory leak
16. Q14 從一千到十萬日活怎麼擴 42:10–47:35
第十四題是前端系統設計:日活從一千長到十萬。純 client-rendered 的情況很單純,靜態資產丟 CDN 就好——但資深的差別在於「怎麼用 CDN」:依變動頻率做選擇性快取,把 React、React DOM 這種穩定 vendor 在 Webpack 或 Vite 層切成長壽 chunk 給很長的 max-age,自己的元件與邏輯切成另一包給幾分鐘的 max-age,使用者才不用每次重下載全部。micro-frontends 則只在開發人數多、想縮小爆炸半徑時才需要,十萬日活單體前端也扛得住。SSR 就複雜得多:所有人都得回你的伺服器渲染,形成瓶頸也帶來延遲。現代解法是把渲染推到邊緣(例如 Cloudflare Workers),但這樣就得連資料庫也放到邊緣:靜態資產、worker 後端、唯讀複本組成一個 edge location,寫入仍要回主資料庫,因此寫很貴、而且只有最終一致性。他也提醒這種架構只有在流量真的分散且效能極關鍵時才值得,快取失效多、出事極難除錯。


推理作者先做了一件跟第十一題一樣的事:**先分辨情境**。他問這是 client-rendered 還是 server-rendered,因為兩者的答案完全不同。client-rendered 的情況很單純,靜態資產丟 CDN 就好;但他馬上指出這正是 junior 與 senior 的分水嶺——大家都會說「用 CDN」,差別在於**怎麼用**。他的做法是依變動頻率做選擇性快取:在 Webpack 或 Vite 層把 React、React DOM 這種穩定 vendor 切成長壽 chunk 給很長的 max-age,自己的元件與邏輯切成另一包給幾分鐘的 max-age,使用者才不必每次重下載全部。這條推理的漂亮之處在於它把 build 設定與快取策略連起來——你怎麼切 bundle,決定了你能不能分開快取。至於 micro-frontend,他自己給了限縮:只有開發人數多時才需要,十萬日活單體前端也撐得住。接著他處理 SSR:所有人都得回伺服器渲染,形成瓶頸也帶來延遲。現代解法是把渲染推到邊緣,但這樣就得連資料庫也放到邊緣——靜態資產、worker 後端、唯讀複本組成一個 edge location,寫入仍要回主庫,所以寫很貴、而且只有最終一致性。最後他自己踩煞車:這種架構複雜、快取失效多、出事極難除錯,多數情況不值得。
AI 補充這裡有一個他沒點破、但是整套快取策略成立的前提:**內容雜湊檔名**。長 max-age 之所以安全,是因為檔名裡帶著內容雜湊(`vendor.a3f9c2.js`),內容一變檔名就變,等於新的資源,根本不需要等快取過期。所以正確的組合是「HTML 不快取或短快取、帶雜湊的資產設 `max-age=31536000, immutable`」,而不是靠猜幾天該重新檢查。他說的「七天檢查一次」在有雜湊檔名的情況下其實太保守了。另外 vendor 分包這個做法近年有個反轉值得知道:當所有資產都走 HTTP/2 或 HTTP/3、而且部署頻繁時,把 vendor 獨立出來的收益會下降,因為 vendor 版本一升級整包就失效,反而不如按路由切分來得實在。至於邊緣的最終一致性,他提到 CAP 但沒說到前端最痛的那個具體症狀:使用者剛送出的寫入,重新整理後看不到自己剛剛改的東西。實務上的緩解是「讀自己的寫入」——寫入後短時間內把該使用者的讀取導回主庫,或直接用本地樂觀更新蓋住那段延遲。
術語:edge computingCAP theorem
17. Q15 LLM 聊天機器人用哪種即時協定 47:35–50:40
最後一題問把 LLM 聊天機器人接進前端要用什麼即時通訊協定。polling 最好做、最穩,但等於自己 DDoS 後端,一萬個 client 乘上去就把後端打掛,擴展性差。WebSocket 開的是雙向通道,很適合真人對真人的聊天,但後端負擔重也較複雜。而 LLM 的通訊是不對稱的:client 送一次 query,模型回一串一串的 token。所以最合適的是 Server-Sent Events——一個 POST 帶上對話,然後在那個端點上聽,模型生成多少就收多少。它是瀏覽器原生 API,連函式庫都不用,OpenAI 的套件底下也是這個;打開 ChatGPT 或 Claude 的 network tab,在 event stream 分頁就看得到 token 一顆顆進來。


推理作者用一貫的方式排序候選方案,每個都先給優點再給致命傷。polling 最好做、最穩,但等於自己 DDoS 後端——他還刻意接回上一題的數字:一萬個 client 乘上去,後端就掛了。WebSocket 開的是雙向通道,很適合真人對真人的聊天,因為訊息真的是雙向的,但後端負擔重也較複雜。到這裡他做了整段的關鍵一步:回頭看**通訊的形狀**。LLM 的通訊是不對稱的——client 送一次 query,模型回一長串 token。既然形狀不對稱,就不需要付雙向通道的代價,Server-Sent Events 正好對上:一個 POST 帶上對話,然後在那個端點上聽,模型生成多少就收多少。而且它是瀏覽器原生 API,連函式庫都不用;OpenAI 的套件底下也是這個,打開 ChatGPT 或 Claude 的 network tab,在 event stream 分頁就看得到 token 一顆顆進來。這個「用資料流的形狀決定協定」的推法,正是整支影片想示範的資深判斷。
- CServer-Sent Events:一條長開的 HTTP 回應單向推文字事件,瀏覽器原生;對上 LLM「送一次、收一長串」的不對稱形狀→ 畫地圖
- A用資料流的形狀選協定:真人聊天是雙向所以 WebSocket,LLM 是不對稱所以 SSE→ 批判類比
AI 補充結論是對的,但有兩個實務細節會讓這個答案更完整。第一,**原生的 `EventSource` 只能發 GET,而且不能自訂 header**——所以真正在做 LLM 串流時,多數人並不是用 EventSource,而是用 `fetch` 加 `POST`,再自己讀 `response.body` 這個串流並解析 SSE 格式(`data:` 開頭的行)。作者說「用純 JavaScript 就能做」是對的,但省略了這一步,照 `EventSource` 的字面去做會卡在無法傳送對話內容與 API 金鑰。第二,**取消**在 LLM 場景特別重要:使用者按下停止時要能真的中止生成,用 fetch 搭配 `AbortController` 很自然,這也是另一個偏好 fetch 而非 EventSource 的理由。最後補一個他沒提的取捨:SSE 走的是單一 HTTP 連線,在 HTTP/1.1 下每個網域的並行連線數有限(通常六條),多個分頁同時開串流可能互相卡住;HTTP/2 之後這個限制才消失。
4. 總結
整支影片其實只有一個主張:資深不在於知道結論,而在於答案裡看得見一條可驗證的推理鏈。它先用一顆 hover 會放大的按鈕示範這條鏈能推到多深——從 reflow 推到 compositor thread,再推到「能繞過 main thread 就繞過」。接著把視角從單一 bug 拉到工作流:與其事後修,不如先用 plan mode、design system 與結構性約束限制 AI 的產出空間;限制之後還要能持續檢查,於是品質被切成 static 與 dynamic 兩側,static 因為成本固定而優先,dynamic 則要靠 testing pyramid 避免掉進「只寫 e2e」的陷阱。談測試時反覆出現的判準「找一個 bug 要燒多少 token」,直接把成本抬成架構變數,於是問題被翻譯回老問題——用最少的改動交付功能,也就是標準化與解耦。標準化展開成 design system 的四層,而 design system 保證不了版面在不同螢幕上不壞,於是回到最底層:box model 決定一個元素佔多大,specificity 決定哪條規則說了算,響應式三支柱決定版面怎麼適應。CSS 這一側收完,卡頓的另一半交給 event loop——單執行緒、兩個佇列、與 rendering 共用同一個 CPU,這條結構同時解釋了打字卡頓的線上 bug 與 React 該怎麼優化。最後兩題把尺度拉到最大:擴展的關鍵不是「用 CDN」而是怎麼依變動頻率切 bundle 分開快取,而即時協定的選擇則要看資料流的形狀——LLM 的通訊是不對稱的,所以 SSE 而不是 WebSocket。整條鏈收束成一個循環:越懂底層,越能給 AI 正確的邊界;邊界越好,改動越少;改動越少,成本越低。
勘誤總整理
確定錯誤/已過時見仁見智(取決於版本或情境)
| 段落 | 原話(transcript 逐字) | 說明 |
|---|---|---|
| 2. Q1 hover 放大:reflow 還是 compositor 1:36 |
「Because CSS transitions they bypass the reflow and they go straight to what we call the compositor thread.」 | 繞過 reflow 的是特定屬性,不是 transition 這個機制本身。只有可合成的屬性(實務上主要是 transform 與 opacity,以及 filter)能直接交給 compositor;`transition: width` 或 `transition: top` 一樣會每格觸發 layout 與 paint。他後面舉的例子(scale)剛好是對的,但把理由歸給「transitions」會讓人誤以為只要寫成 transition 就便宜。 依據: CSS Triggers / Chrome 渲染管線文件:layout → paint → composite,僅 transform、opacity 等屬性可跳到 composite |
| 8. Q7 CSS box model 與 box-sizing 23:02 |
「However, the default of the browser is extrinsic width.」 | 說反了。瀏覽器的預設是 box-sizing: content-box,而尺寸來源的預設是 intrinsic(由內容或可用空間決定),不是 extrinsic。extrinsic 指的正是你自己寫死 width 的那種情況。他後面描述的行為(瀏覽器先算內容寬、再把 padding 加上去)其實就是 content-box 的行為,講法與用詞在這裡對不上。 依據: CSS Box Sizing Level 3:初始值 box-sizing: content-box;intrinsic sizing 指 max-content / fit-content 等由內容決定的尺寸 |
| 11. Q10 event loop 的完整運作 32:55 |
「And we don't immediately pick the microtask.」 | 他描述成「執行一個 microtask 後就停下來去做 rendering,下一輪才取下一個」。實際規格是:每個 macrotask 結束後,microtask queue 會被**整個排乾**(包含執行過程中新增的),然後才輪到 rendering。這個差別有實際後果——不斷產生 microtask 的迴圈會讓畫面永遠不更新,而同樣的工作排成 macrotask 則不會。 依據: HTML Living Standard, event loop processing model:perform a microtask checkpoint 在每個 task 之後、update the rendering 之前 |
| 10. Q9 響應式設計的三根支柱 28:50 |
「So, you never want to use pixels, for example.」 | 太絕對。該用相對單位的是會隨使用者字級設定或容器變動的尺寸(字級、間距、元素寬度);border 寬度、細線、某些圖示尺寸用 px 反而正確,因為它們不該跟著字級放大。真正的問題不是 px 本身,而是把應該跟著容器或字級走的尺寸寫死。 依據: WCAG 1.4.4 要求文字可縮放至 200%,指向字級用相對單位;但並未要求所有長度都用相對單位 |
5. 推薦三個下一步
1. 往下挖深:瀏覽器渲染管線與效能量測
影片把 reflow、compositor thread、critical rendering path 都當成已知前提快速帶過,但沒有示範怎麼在 DevTools 裡實際看到它們。補上這一塊,前面關於 transform、re-render、載入速度的判斷才能被自己驗證,而不是背下來的結論。
YouTube 搜尋:browser rendering pipeline layout paint composite Chrome DevTools performance panel tutorial Core Web Vitals INP LCP debugging
2. 往旁邊對照:現代 CSS 的版面新工具
作者的響應式三支柱停在 rem / flex / breakpoint,還把 grid 說成需要大量 media query。但 container queries、subgrid 與 auto-fit 這些能力已經改變了「元件如何自我響應」的答案,跟他自己在 design system 那一段的目標其實更契合。
YouTube 搜尋:CSS container queries tutorial CSS grid auto-fit minmax responsive without media queries modern CSS layout 2026
3. 往上應用:把 LLM 串流真的接進前端
最後一題只講到「用 SSE」就結束,但實作上最卡的地方在後面——原生 EventSource 不能 POST、要用 fetch 自己解析串流、還要處理中止、重連與 token 的漸進渲染。實際做一次,前面關於 event loop 與 re-render 的知識會全部用上。
YouTube 搜尋:streaming LLM response fetch ReadableStream SSE AbortController cancel streaming request React Vercel AI SDK streaming chat tutorial
- 相關 —Embrace Reactive Programming in React and Vue with RxJS - Fahad Ibnay Heylaal · event loop、re-render 成本與記憶體回收,是把 props 當 stream 的前提
- 相關 —Message Queues in System Design Interviews w/ Meta Staff Engineer · 解耦與最終一致性的取捨,在前端 edge 擴展與即時協定選擇上重演一次
- 接著看 →3 Frontend Skills AI Can't Replace (become AI-proof) · 面試站點到的「AI 壞習慣:硬寫像素、重複 state」,在這站被展開成 code smell 與靜態分析
- 接著看 →Every Frontend Architecture Pattern Explained in 23 Minutes · 面試題只點到 monolith 與 micro-frontend,這站把整條架構演化與六軸取捨攤開
- 接著看 →Real Senior Frontend System Design Interview 2026 (AI Coding Included) · 面試題的分類式答法與即時協定判準(SSE/WebSocket/Polling),在這站變成一整場完整設計
- 接著看 →Frontend System Design Explained w/ Senior Engineer (Microfrontends, Monorepo, MCP UI, Reactjs) · 面試題點到的 bundle 切分、即時協定、Core Web Vitals,在這站各自展開成機制與判準
- 接著看 ←15 Frontend Concepts Every Senior Dev Has Mastered · 同一批模型換成 15 道面試題,示範怎麼在口頭作答時組織它們
- 接著看 →9 JavaScript Concepts That Got Me To Senior Dev · 面試題只點到 event loop 與記憶體回收,這站把 call stack、兩個佇列與 GC 的機制整條攤開
- 相關 —How Senior Frontend Engineers Think in System Design Interviews · 同為前端面試準備,共用 WebSocket/polling:一支給決策方法,一支給答題清單
- 相關 —Real Frontend System Design (from a Senior Engineer) · 共用 CAP、CDN、edge computing、read replica:一站是分題速答,一站是一條完整推理線
- 相關 —Senior Frontend Interview 2026 🎉 | Javascript 🎯 (Mock) [Most Asked Questions] · 同樣是面試題:一邊是整理好的標準答案,一邊是真實臨場的卡關與修正
- 相關 —15 Fullstack Concepts Every Frontend Should Know · 共用 SSE/WebSocket/max-age:一站解釋機制,一站是面試速答清單
- 相關 —How Frontend Engineers can master the Full-stack · 共用 CAP、最終一致性、唯讀副本、max-age 與即時協定選型
- 接著看 ←You Wouldn't Believe These Developer Interview Mistakes... · 這站只說「基本功一直把人考倒」,那站直接給 15 題的答法
- 接著看 →Frontend System Design: Performance API, Chrome, React Profiler · 面試題點到 main thread 阻塞與 code splitting,這站給證明改動有沒有用的量法
- 相關 —Core Web Vitals Explained: LCP, INP & CLS · main thread 與 reflow 在面試題裡以問答形式再出現一次
- 相關 —Reactivity in Vue 3 - How does it work? · 那邊考 Map、WeakMap 與 GC,這站拿它們當依賴的儲存結構
- 接著看 →JavaScript Visualized - Promise Execution · 面試題站點到 microtask queue 就停;這站補上 handler 何時被排進去
- 接著看 ←JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue · 面試題直接考 event loop 的執行順序,先在這站把規則推熟