HTML 演算法跟上 Fetch 腳步:同步旗標走入歷史
WHATWG HTML commit · 2026-09-03
背景
Fetch 標準過去內建一個稱為 synchronous flag 的旗標,作用是告訴 fetch 演算法「這次呼叫必須阻塞呼叫端的執行緒直到收到回應」,最典型的使用場景是同步版本的 XMLHttpRequest,以及 Worker 內呼叫 importScripts() 載入指令碼。Fetch 規範本身早已拿掉這個旗標,在 commit 12dd6fa8 中改成「要不要真的阻塞執行緒是呼叫端自己的責任」,fetch 演算法只透過回呼函式回傳結果,不再承諾同步返回。問題是 HTML 規範裡還留著四個呼叫點繼續在設定這個早已從 Fetch 規範中消失的旗標,形同一段沒清乾淨的技術債,直到這次 commit 8a9acc4c 才一次補齊。
核心改動
這次修改的四個呼叫點分別是 Download the hyperlink(<a download> 的下載流程)、預設 favicon 抓取、img 元素的 React to changes in the environment,以及更早在 commit c196f1bc 就已經先行更新、只差旗標沒清掉的「預設抓取並處理連結資源」演算法。三個尚未更新的演算法統一改成把後續處理包裝成 processResponse 回呼函式傳給 fetch,不再需要額外包一層「in parallel」的平行執行語意來模擬非同步。影響最大的是 React to changes in the environment:後續步驟被重新巢狀化,並且把它與「已經有快取回應」分支共用的收尾邏輯抽成一個新的輔助流程 finishUpdate,避免同一段步驟被描述兩次。
// before(概念示意)
set request's synchronous flag
let response be the result of fetching request
// 阻塞直到完成才繼續
finish using response
// after
fetch request with processResponse
given response: finish using response
// 不再需要 in parallel 或同步旗標
影響範圍
由於 Chromium、Gecko、WebKit 這些瀏覽器引擎原本就是以非同步方式實作這些流程,這次修改本質上是編輯層級的清理,不會改變使用者可觀察到的行為,純粹是讓規範文字追上真實世界的實作現況。真正受影響的是規範維護者,以及需要逐字對照演算法步驟驗證瀏覽器合規性的實作者:一旦四個呼叫點都拿掉了同步旗標,之後若有新演算法想要引用 fetch,就不會再誤用一個規範中已不存在的機制,也讓 HTML 規範裡「in parallel」的使用範圍更貼近實際需要平行處理的場景。
- Download the hyperlink(
<a download>) - 預設 favicon 抓取流程
- img 元素的 React to changes in the environment
- 預設抓取並處理連結資源(前次 commit 已更新,此次補上旗標清理)
原始來源:WHATWG HTML commit 8a9acc4c、WHATWG Fetch commit 12dd6fa8
viewport meta 該減重了:initial-scale=1 在 2026 年已無用武之地
vale.rocks · 2026-09-02
原本的問題
iPhone 於 2007 年問世後,Safari 引入了 <meta name="viewport"> 這個後來被廣泛採納的標籤,目的是讓那些原本針對桌面設計、寬度動輒 980px 的網站,也能在小螢幕上正常縮放顯示,其中 width=device-width 讓版面寬度直接對齊裝置的 CSS 像素寬度,是響應式設計最基本的一行設定。問題出在 initial-scale:早期 iOS 與部分行動瀏覽器即使寫了 width=device-width,頁面首次載入時仍可能被瀏覽器自動放大或縮小,因此業界慣例(包括 Apple 自家的 Safari Web Content Guide 與 CSS-Tricks 等文章)都建議額外加上 initial-scale=1.0 來鎖死初始縮放比例。
採用的方法
作者實際在目前主流的行動瀏覽器上測試,只寫 width=device-width、完全不指定 initial-scale,初始縮放比例本來就已經是 1,因為當年造成非預期縮放的舊版瀏覽器早已從市場上消失。換句話說,initial-scale=1.0 是一段只為了繞開已經死去的瀏覽器 bug 而存在的防禦性設定,在今日的瀏覽器環境裡已經沒有實際作用。因此文章給出的建議很直接:把 viewport meta 內容精簡成 <meta name="viewport" content="width=device-width">,拿掉 , initial-scale=1 這 19 個 bytes。
實際效果
19 bytes 看起來微不足道,但這個標籤幾乎出現在網路上每一個 HTML 文件的 <head> 裡,省下的位元組會隨著頁面瀏覽次數線性放大,對高流量網站而言,長期下來仍是可觀的傳輸量節省。更直接的好處是少了一段不再有人記得原因、只會被複製貼上到新專案樣板裡的歷史包袱,往後新專案設定 viewport 時,也少一個需要向團隊成員解釋來由的參數。
效能優化真正的戰場:那條被塞爆的瀏覽器主執行緒
kciter.so · 2026-07-12
原本的問題
多數前端效能優化的討論都圍繞在網路層——bundle size、快取策略、CDN,但作者指出,一旦頁面進入互動階段,真正決定「順不順」的瓶頸其實是主執行緒(main thread)。主執行緒同時要跑 JavaScript,也要負責樣式計算、版面配置與繪製,渲染流程裡只有最後的合成(compositing)步驟會被丟到獨立的合成執行緒處理。以 60Hz 螢幕為例,每個 frame 理論預算是 16.6ms,扣掉瀏覽器自身開銷後,實務上大概只剩 10ms 左右可以自由運用,單一任務超過 50ms 就會被視為需要處理的 long task。因為 JS 執行與畫面渲染排在同一條隊伍裡,長任務跑著的當下,使用者的輸入、動畫、捲動全部都會被卡住。
採用的方法
文章把解法分成兩大類:切分(splitting)與合併(batching)。切分是把一次做完的長工作拆成多個小塊,透過 setTimeout、requestAnimationFrame,或是較新的 scheduler.yield() 把控制權讓回主執行緒,讓瀏覽器有機會插入處理使用者輸入;文章以聊天室訊息渲染為例,改成每渲染 20 則訊息就 yield 一次,即使總運算量沒有變,輸入的即時反應就恢復了。合併則是對高頻率觸發的事件做 debounce 或 throttle,例如 markdown 編輯器只要把預覽區塊的重繪加上 debounce,打字延遲就消失。對於像 JSON.parse 這種無法被中途中斷的原子操作,文章建議乾脆丟進 Web Worker,徹底離開主執行緒。scheduler.yield() 目前已在 Chrome、Edge 與 Firefox 支援,但 Safari 尚未實作,考慮瀏覽器覆蓋率時仍需搭配 setTimeout 作為降級方案。
實際效果
這些技巧都不會減少總運算量,只是把工作切開分散到多個 frame,或是移到另一條執行緒去跑,讓主執行緒有空檔能回應輸入與更新畫面。切分本身並非沒有代價:setTimeout 有最低延遲限制,頻繁的 yield 也會帶來排程開銷,因此切分的粒度(例如上述的「每 20 則訊息」)需要視實際工作量調整,而不是切得越細越好。CSS 動畫因為執行在合成執行緒上,即使主執行緒被長任務佔滿也能繼續播放,是少數天生不受主執行緒阻塞影響的例外。