當使用者在某個分頁登出時,其他分頁也應該跟著登出。當他們更新購物車時,每個已開啟的視窗都應該反映這個變更。常見的解法是玩一個 localStorage 小技巧:寫入一個哨兵值,監聽 storage 事件,讀取它、解析它、檢查它是不是「給你看的」,再把它清掉。這方法可行——但它是建立在一個原本不是用來傳訊息的持久化 API 上的側通道通訊模式。Broadcast Channel API 才是直接的解法。
// 傳送端(同源下的任何分頁、worker 或 iframe)
const channel = new BroadcastChannel('app-sync');
channel.postMessage({ type: 'LOGOUT' });
// 接收端(訂閱同一名稱的所有其他內容)
const channel = new BroadcastChannel('app-sync');
channel.onmessage = (event) => {
console.log(event.data); // { type: 'LOGOUT' }
};
兩個步驟:先用名稱開啟一個頻道,再傳送或監聽。任何在同源下、以相同名稱開啟頻道的分頁、worker 或 iframe,都會收到在這個頻道上送出的每一則訊息——包括它們「訂閱之後」送出的訊息。傳送端不會收到自己的訊息。
用完後請關閉頻道,以釋放監聽器:
channel.close();
使用 storage 事件來做跨分頁同步的典型模式:
// 傳送端
localStorage.setItem('__broadcast', JSON.stringify({ type: 'LOGOUT', t: Date.now() }));
localStorage.removeItem('__broadcast'); // 立即清理
// 接收端
window.addEventListener('storage', (event) => {
if (event.key !== '__broadcast') return; // 過濾雜訊
if (!event.newValue) return; // 忽略 removeItem
const message = JSON.parse(event.newValue);
if (message.type === 'LOGOUT') { /* 處理 */ }
});
這裡的每一部分都是不得不的變通方案:時間戳記用來避免相同值重複送出時被去重;removeItem 會觸發第二個 storage 事件,必須過濾掉;因為儲存空間只支援字串,所以需要 JSON.stringify / JSON.parse。BroadcastChannel 用一次 postMessage 就能取代整段程式。
跨所有分頁登出。 使用者登出時,立即讓每個開啟的視窗同時使工作階段失效:
// auth.js — 在每個分頁都會執行
const syncChannel = new BroadcastChannel('auth');
export function logout() {
clearSession();
syncChannel.postMessage({ type: 'SESSION_ENDED' });
redirect('/login');
}
syncChannel.onmessage = (event) => {
if (event.data.type === 'SESSION_ENDED') {
clearSession();
redirect('/login');
}
};
電商應用中的購物車同步。 在一個分頁加入購物車時,其他分頁的頁首數量也會跟著更新:
const cartChannel = new BroadcastChannel('cart');
function addToCart(item) {
const updated = updateLocalCart(item);
cartChannel.postMessage({ type: 'CART_UPDATED', cart: updated });
renderCart(updated);
}
cartChannel.onmessage = (event) => {
if (event.data.type === 'CART_UPDATED') {
renderCart(event.data.cart);
}
};
即時設定更新。 當管理員在設定分頁變更某個功能旗標時,廣播這個更新,讓其他已開啟的分頁在不重新整理頁面的情況下也能立即接收到。
BroadcastChannel 使用 structured clone 演算法——也就是 workers 上 structuredClone() 和 postMessage() 所使用的同一套機制。這代表你可以傳送:
Date、Map、Set、ArrayBuffer、Blobnull你不能傳送函式、DOM 節點,或任何無法被 structured clone 序列化的內容。如果嘗試這麼做,呼叫會拋出 DataCloneError。對於多數實際應用會使用的訊息內容——具有型別欄位的事件物件——structured clone 已經涵蓋所有需求,不需要再經過 JSON 來回轉換。
BroadcastChannel 的作用範圍是同源內容——相同的協定、主機名稱與埠號。https://example.com 上名為 'app-sync' 的頻道,和 https://staging.example.com 上同名頻道是完全隔離的。你不能用它來讓不同來源之間互相通訊。
頻道名稱就是你的命名空間。如果你的應用中有多個功能都要使用 BroadcastChannel,請替每個功能取不同名稱('auth'、'cart'、'notifications'),不要共用單一的 'app' 頻道再用訊息類型來做多工處理——分開的頻道更乾淨,也不需要過濾訊息。
BroadcastChannel 屬於 Baseline 2022:Chrome 54(2016)、Firefox 38(2015)、Safari 15.4(2022 年 3 月)。這個 API 在 Chromium 與 Firefox 上已經存在將近十年;Safari 則在 2022 年加入。它可用於所有目前支援的瀏覽器版本,也可用於 Web Worker 與 Service Worker,不只限於主執行緒。
<!-- playground:start -->
直接在你的瀏覽器中執行——動手操作看看,觀察概念即時反應。
<!-- playground:end -->
<!-- quiz:start -->
覺得有點懂了嗎?來做這份 9 題測驗 →
即時回饋、每題都有提示,並且每個答案——不管對錯——都附有說明。
<!-- quiz:end -->
請在你的程式碼庫中搜尋那些搭配 localStorage.setItem 使用、而且會立刻被移除的 storage 事件監聽器。那種模式其實就是透過儲存空間側通道進行跨分頁訊息傳遞——而這正是 BroadcastChannel 要取代的東西。把它換掉:用名稱開啟頻道、呼叫 postMessage、用 onmessage 監聽。你會得到不需要序列化的結構化資料、不必過濾 storage 事件雜訊,也不需要管理清理用的哨兵值。程式碼中的意圖會更清楚;而實際的傳遞工作則交給執行環境處理。
感謝閱讀!一起保持聯繫: