題幹與適用場景
一個管理後台允許使用者同時開啟多個分頁。使用者在一個分頁登出、切換主題或更新通知後,其他分頁應盡快反映變化;重新整理、休眠、重複訊息和不支援 BroadcastChannel 的瀏覽器不能破壞最終狀態。請設計同步協定、復原策略、降級方案和驗證指標。
這是面向前端、Web 平台和前端系統設計職位的瀏覽器協作題。分頁數量、同步延遲和事件類型是面試假設,不是瀏覽器保證。重點是理解 BroadcastChannel 的同源/儲存分區邊界、訊息不持久化的後果、狀態與事件的取捨,以及 localStorage、IndexedDB、SharedWorker 或 Web Locks 的組合條件。
面試官考察點
第一,能否說清 API 邊界。BroadcastChannel 讓同一 origin 且處於可通信 storage partition 的視窗、分頁、iframe 和 worker 互傳訊息;它不是跨來源通道、持久佇列或分散式一致性服務。
第二,能否設計冪等協定。訊息可能重複,傳送者收不到自己的廣播,接收頁面可能剛好關閉或休眠;只傳「主題變了」而沒有版本和重新讀取路徑,會留下永久過期狀態。
第三,能否區分事件通知和狀態來源。登出可廣播失效提示,主題可廣播後寫入持久化設定,通知列表應讓接收者重新讀取權威快取,而不是把整份列表當作真相。
最後,能否處理生命週期與降級:關閉 channel、避免把權杖放入訊息、能力偵測、使用 storage 事件或伺服器重新拉取,並用可觀測指標證明同步沒有製造迴圈和記憶體洩漏。
回答前需要釐清的問題
- 要同步的是一次性事件、目前狀態還是可重播歷史?這決定是否需要持久化版本。
- 通訊範圍是同一 origin、同一頂層網站下的 iframe,還是跨子網域?storage partition 可能讓「同源」仍無法互通。
- 遺失一則訊息是否可接受?登出、權限撤銷與編輯衝突的復原要求不同。
- 狀態真相在哪裡:伺服器、IndexedDB、localStorage、記憶體快取還是 Service Worker?
- 是否需要保證只有一個分頁執行重新整理、同步或寫入?若需要,廣播本身不提供鎖。
- 瀏覽器相容矩陣、私密模式、背景凍結和多視窗數量是多少?
- 訊息是否可能包含個人資料、權限或權杖?若包含,應改為不帶敏感內容的失效通知。
30 秒回答框架
「我會把 BroadcastChannel 當作低延遲通知總線,不當作持久狀態來源。每則訊息帶協定版本、事件類型、單調序列或狀態版本和 trace ID;接收者先驗證來源與結構,再按事件冪等處理,發現版本跳躍就從 localStorage、IndexedDB 或伺服器重新讀取。登出只發送失效訊號,主題寫入持久化設定,通知變化觸發重新拉取。能力偵測失敗時降級到 storage 事件或定期拉取;需要單一寫入者時使用 Web Locks 或伺服器協調。所有 channel 在卸載時關閉,並用指標驗證延遲、遺失復原和重複處理。」
分步驟深入解答
第一步:定義訊息與權威狀態
為每個功能設定狀態來源。主題和語言是使用者偏好,可由持久化設定保存;通知列表和權限應由伺服器或本地快取重新讀取;登出是工作階段失效訊號,不能把 access token 放進訊息。廣播只攜帶不敏感的 type、version、entityKey、updatedAt 或 traceId。
MDN 說明 BroadcastChannel 允許同源瀏覽上下文和 worker 雙向通訊,但訊息協定由應用自行定義,沒有自動協商。協定版本、未知事件處理和欄位驗證必須由前端明確約定。
第二步:處理重複、亂序與遺失
每個狀態域維護最後處理版本。接收訊息時,版本小於或等於本地版本直接忽略;版本相鄰可觸發一次讀取;出現跳躍則標記 gap 並從權威來源重新同步。若業務只能提供事件而沒有版本,至少使用去重 ID 和短期已處理集合,但它無法證明中間事件沒有遺失。
不要假設廣播可靠送達。傳送者不會收到自己的訊息,剛開啟或已休眠的分頁可能錯過事件。因此頁面啟動時必須先讀取持久化狀態或伺服器快照,再訂閱 channel;訊息只是縮短新鮮度延遲。
第三步:選擇持久化與協調組合
localStorage 適合小型偏好和觸發 storage 事件;它不是高吞吐資料庫,也不適合存放權杖。IndexedDB 適合較大本地快取和版本化快照。Service Worker 可以作為背景同步參與者,但它也應把結果寫入可復原儲存。
BroadcastChannel 解決「通知多個上下文」;它不保證單一寫入者。若只有一個分頁可以刷新快取、執行遷移或提交批次,使用 Web Locks;若鎖不可用,就讓操作帶版本條件並在伺服器端裁決。SharedWorker 適合共用連線或集中狀態,但增加生命週期和相容複雜度。
第四步:實作安全的接收與降級
建立 channel 後只接受白名單事件,限制訊息大小,拒絕未知版本和不符合 schema 的物件。訊息中不放 access token、完整使用者資料或可直接執行的 HTML。對登出事件立即清除本地敏感快取並導向登入頁;對主題事件更新 UI,但仍以持久化值為準。
能力偵測失敗時優先使用 storage 事件同步小型狀態;若瀏覽器或分區仍不支援,再採用視窗取得焦點時重新拉取、短輪詢或伺服器推送。降級策略必須允許重複執行,不能讓「沒有廣播」變成永久不同步。
第五步:管理資源與驗證指標
元件或頁面銷毀時移除監聽器並呼叫 close();不要每次渲染都建立新 channel。統一命名空間,避免不同應用誤訂閱同名頻道。給訊息增加來源標籤和 trace ID,防止一個分頁收到後再次廣播造成迴圈。
驗證至少覆蓋:多分頁端到端延遲、重複/亂序、休眠後復原、重新整理初始快照、storage 降級、storage partition 隔離、channel 關閉、異常大訊息、訊息迴圈和多使用者切換。指標包括事件處理成功率、版本 gap 次數、重新拉取次數、重複丟棄率、復原耗時和未關閉 channel 數量。
高品質示範回答
「我會先把 BroadcastChannel 定義為通知總線,不把它當作佇列。登出事件只帶工作階段失效類型和版本;主題事件寫入 localStorage 後廣播新的設定版本;通知列表只廣播實體鍵和版本,接收分頁重新從伺服器或 IndexedDB 讀取。訊息不含權杖和完整使用者資料。
每則訊息有協定版本、單調狀態版本和 trace ID。接收者拒絕未知結構,已處理或更舊版本直接丟棄,發現版本跳躍就重新拉取快照。頁面啟動先載入快照再訂閱,因為剛開啟或休眠的分頁可能錯過廣播,傳送者也收不到自己的訊息。
如果需要單一寫入者刷新快取,我會加 Web Locks;只靠 BroadcastChannel 不能防止兩個分頁同時寫入。能力不支援時,小型偏好降級到 storage 事件,其他資料在取得焦點或短輪詢時重新讀取。元件銷毀要移除監聽並關閉 channel,指標觀察延遲、gap、重複、復原時間和未關閉資源。這樣把低延遲通知、可復原狀態和瀏覽器相容性分開處理。」
常見錯誤
- 把廣播當持久佇列 → 新開或休眠分頁會漏事件 → 啟動先讀快照,訊息只做增量提示。
- 訊息直接攜帶 token 或完整狀態 → 擴大敏感資料暴露和版本衝突 → 只傳不敏感事件、鍵和版本。
- 沒有版本或去重 ID → 重複、亂序會回退 UI → 單調版本、冪等處理和 gap 重讀。
- 把同源等同於必然互通 → storage partition 可能隔離上下文 → 驗證實際通信範圍並準備降級。
- 用 BroadcastChannel 實作鎖 → 兩個分頁仍可能同時寫入 → 使用 Web Locks 或伺服器條件寫。
- 每次渲染建立 channel → 監聽器和資源洩漏 → 穩定實例、卸載時移除並 close。
- 接收後無條件再廣播 → 形成訊息迴圈 → 保留來源/trace ID,只由狀態變化觸發發送。
- 把 storage 事件當完整替代品 → 它只覆蓋部分寫入場景且不傳給同一視窗 → 定義能力差異並補充重新拉取。
追問及應對
追問一:使用者在兩個分頁同時編輯同一表單,如何避免覆蓋?
廣播只通知「資源版本變了」,不直接覆蓋本地草稿。提交時帶基線版本,伺服器用條件寫拒絕過期版本;前端展示衝突並讓使用者合併。若只是本地偏好,可用 Web Locks 串行寫入。
追問二:一個分頁休眠 20 分鐘後恢復,如何追上狀態?
恢復時重新讀取伺服器或 IndexedDB 快照,比較狀態版本後再處理增量。不要依賴快取的最後一則廣播;若快照不可用,標記需要重新驗證或展示明確的過期狀態。
追問三:不同子網域的頁面需要互通,BroadcastChannel 能解決嗎?
不能直接假設可以。BroadcastChannel 受同源和 storage partition 限制;跨來源通訊需要明確的 postMessage 視窗關係或伺服器協調,並驗證 origin、訊息 schema 和權限,不能透過放寬安全邊界來「共用頻道」。
追問四:需要保證只有一個分頁維護 WebSocket,如何設計?
BroadcastChannel 可傳播連線狀態和資料,但不提供選主。優先用 Web Locks 選持有者,其他分頁訂閱廣播;持有者關閉或失去鎖後重新選主。若瀏覽器能力不足,採用伺服器租約或允許多個連線並用伺服器去重,明確成本取捨。